summaryrefslogtreecommitdiffstats
path: root/src/backend/commands/variable.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
commit46651ce6fe013220ed397add242004d764fc0153 (patch)
tree6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/commands/variable.c
parentInitial commit. (diff)
downloadpostgresql-14-46651ce6fe013220ed397add242004d764fc0153.tar.xz
postgresql-14-46651ce6fe013220ed397add242004d764fc0153.zip
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/commands/variable.c')
-rw-r--r--src/backend/commands/variable.c935
1 files changed, 935 insertions, 0 deletions
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
new file mode 100644
index 0000000..0c85679
--- /dev/null
+++ b/src/backend/commands/variable.c
@@ -0,0 +1,935 @@
+/*-------------------------------------------------------------------------
+ *
+ * variable.c
+ * Routines for handling specialized SET variables.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/variable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <ctype.h>
+
+#include "access/htup_details.h"
+#include "access/parallel.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "catalog/pg_authid.h"
+#include "commands/variable.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+#include "utils/varlena.h"
+
+/*
+ * DATESTYLE
+ */
+
+/*
+ * check_datestyle: GUC check_hook for datestyle
+ */
+bool
+check_datestyle(char **newval, void **extra, GucSource source)
+{
+ int newDateStyle = DateStyle;
+ int newDateOrder = DateOrder;
+ bool have_style = false;
+ bool have_order = false;
+ bool ok = true;
+ char *rawstring;
+ int *myextra;
+ char *result;
+ List *elemlist;
+ ListCell *l;
+
+ /* Need a modifiable copy of string */
+ rawstring = pstrdup(*newval);
+
+ /* Parse string into list of identifiers */
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ {
+ /* syntax error in list */
+ GUC_check_errdetail("List syntax is invalid.");
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+
+ 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 check_datestyle(...)" because we need
+ * to handle constructs like "DEFAULT, ISO".
+ */
+ char *subval;
+ void *subextra = NULL;
+
+ subval = strdup(GetConfigOptionResetString("datestyle"));
+ if (!subval)
+ {
+ ok = false;
+ break;
+ }
+ if (!check_datestyle(&subval, &subextra, source))
+ {
+ free(subval);
+ ok = false;
+ break;
+ }
+ myextra = (int *) subextra;
+ if (!have_style)
+ newDateStyle = myextra[0];
+ if (!have_order)
+ newDateOrder = myextra[1];
+ free(subval);
+ free(subextra);
+ }
+ else
+ {
+ GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
+
+ if (!ok)
+ {
+ GUC_check_errdetail("Conflicting \"datestyle\" specifications.");
+ return false;
+ }
+
+ /*
+ * Prepare the canonical string to return. GUC wants it malloc'd.
+ */
+ result = (char *) malloc(32);
+ if (!result)
+ return false;
+
+ 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;
+ }
+
+ free(*newval);
+ *newval = result;
+
+ /*
+ * Set up the "extra" struct actually used by assign_datestyle.
+ */
+ myextra = (int *) malloc(2 * sizeof(int));
+ if (!myextra)
+ return false;
+ myextra[0] = newDateStyle;
+ myextra[1] = newDateOrder;
+ *extra = (void *) myextra;
+
+ return true;
+}
+
+/*
+ * assign_datestyle: GUC assign_hook for datestyle
+ */
+void
+assign_datestyle(const char *newval, void *extra)
+{
+ int *myextra = (int *) extra;
+
+ DateStyle = myextra[0];
+ DateOrder = myextra[1];
+}
+
+
+/*
+ * TIMEZONE
+ */
+
+/*
+ * check_timezone: GUC check_hook for timezone
+ */
+bool
+check_timezone(char **newval, void **extra, GucSource source)
+{
+ pg_tz *new_tz;
+ long gmtoffset;
+ char *endptr;
+ double hours;
+
+ if (pg_strncasecmp(*newval, "interval", 8) == 0)
+ {
+ /*
+ * Support INTERVAL 'foo'. This is for SQL spec compliance, not
+ * because it has any actual real-world usefulness.
+ */
+ const char *valueptr = *newval;
+ char *val;
+ Interval *interval;
+
+ valueptr += 8;
+ while (isspace((unsigned char) *valueptr))
+ valueptr++;
+ if (*valueptr++ != '\'')
+ return false;
+ val = pstrdup(valueptr);
+ /* Check and remove trailing quote */
+ endptr = strchr(val, '\'');
+ if (!endptr || endptr[1] != '\0')
+ {
+ pfree(val);
+ return false;
+ }
+ *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)
+ {
+ GUC_check_errdetail("Cannot specify months in time zone interval.");
+ pfree(interval);
+ return false;
+ }
+ if (interval->day != 0)
+ {
+ GUC_check_errdetail("Cannot specify days in time zone interval.");
+ pfree(interval);
+ return false;
+ }
+
+ /* Here we change from SQL to Unix sign convention */
+ gmtoffset = -(interval->time / USECS_PER_SEC);
+ new_tz = pg_tzset_offset(gmtoffset);
+
+ pfree(interval);
+ }
+ else
+ {
+ /*
+ * Try it as a numeric number of hours (possibly fractional).
+ */
+ hours = strtod(*newval, &endptr);
+ if (endptr != *newval && *endptr == '\0')
+ {
+ /* Here we change from SQL to Unix sign convention */
+ gmtoffset = -hours * SECS_PER_HOUR;
+ new_tz = pg_tzset_offset(gmtoffset);
+ }
+ else
+ {
+ /*
+ * Otherwise assume it is a timezone name, and try to load it.
+ */
+ new_tz = pg_tzset(*newval);
+
+ if (!new_tz)
+ {
+ /* Doesn't seem to be any great value in errdetail here */
+ return false;
+ }
+
+ if (!pg_tz_acceptable(new_tz))
+ {
+ GUC_check_errmsg("time zone \"%s\" appears to use leap seconds",
+ *newval);
+ GUC_check_errdetail("PostgreSQL does not support leap seconds.");
+ return false;
+ }
+ }
+ }
+
+ /* Test for failure in pg_tzset_offset, which we assume is out-of-range */
+ if (!new_tz)
+ {
+ GUC_check_errdetail("UTC timezone offset is out of range.");
+ return false;
+ }
+
+ /*
+ * Pass back data for assign_timezone to use
+ */
+ *extra = malloc(sizeof(pg_tz *));
+ if (!*extra)
+ return false;
+ *((pg_tz **) *extra) = new_tz;
+
+ return true;
+}
+
+/*
+ * assign_timezone: GUC assign_hook for timezone
+ */
+void
+assign_timezone(const char *newval, void *extra)
+{
+ session_timezone = *((pg_tz **) extra);
+}
+
+/*
+ * show_timezone: GUC show_hook for timezone
+ */
+const char *
+show_timezone(void)
+{
+ const char *tzn;
+
+ /* Always show the zone's canonical name */
+ 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.
+ */
+
+/*
+ * check_log_timezone: GUC check_hook for log_timezone
+ */
+bool
+check_log_timezone(char **newval, void **extra, GucSource source)
+{
+ pg_tz *new_tz;
+
+ /*
+ * Assume it is a timezone name, and try to load it.
+ */
+ new_tz = pg_tzset(*newval);
+
+ if (!new_tz)
+ {
+ /* Doesn't seem to be any great value in errdetail here */
+ return false;
+ }
+
+ if (!pg_tz_acceptable(new_tz))
+ {
+ GUC_check_errmsg("time zone \"%s\" appears to use leap seconds",
+ *newval);
+ GUC_check_errdetail("PostgreSQL does not support leap seconds.");
+ return false;
+ }
+
+ /*
+ * Pass back data for assign_log_timezone to use
+ */
+ *extra = malloc(sizeof(pg_tz *));
+ if (!*extra)
+ return false;
+ *((pg_tz **) *extra) = new_tz;
+
+ return true;
+}
+
+/*
+ * assign_log_timezone: GUC assign_hook for log_timezone
+ */
+void
+assign_log_timezone(const char *newval, void *extra)
+{
+ log_timezone = *((pg_tz **) extra);
+}
+
+/*
+ * show_log_timezone: GUC show_hook for log_timezone
+ */
+const char *
+show_log_timezone(void)
+{
+ const char *tzn;
+
+ /* Always show the zone's canonical name */
+ tzn = pg_get_timezone_name(log_timezone);
+
+ if (tzn != NULL)
+ return tzn;
+
+ return "unknown";
+}
+
+
+/*
+ * SET TRANSACTION READ ONLY and SET TRANSACTION READ WRITE
+ *
+ * We allow idempotent changes (r/w -> r/w and r/o -> r/o) at any time, and
+ * we also always allow changes from read-write to read-only. However,
+ * read-only may be changed to read-write only when in a top-level transaction
+ * that has not yet taken an initial snapshot. Can't do it in a hot standby,
+ * either.
+ *
+ * If we are not in a transaction at all, just allow the change; it means
+ * nothing since XactReadOnly will be reset by the next StartTransaction().
+ * The IsTransactionState() test protects us against trying to check
+ * RecoveryInProgress() in contexts where shared memory is not accessible.
+ * (Similarly, if we're restoring state in a parallel worker, just allow
+ * the change.)
+ */
+bool
+check_transaction_read_only(bool *newval, void **extra, GucSource source)
+{
+ if (*newval == false && XactReadOnly && IsTransactionState() && !InitializingParallelWorker)
+ {
+ /* Can't go to r/w mode inside a r/o transaction */
+ if (IsSubTransaction())
+ {
+ GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
+ GUC_check_errmsg("cannot set transaction read-write mode inside a read-only transaction");
+ return false;
+ }
+ /* Top level transaction can't change to r/w after first snapshot. */
+ if (FirstSnapshotSet)
+ {
+ GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
+ GUC_check_errmsg("transaction read-write mode must be set before any query");
+ return false;
+ }
+ /* Can't go to r/w mode while recovery is still active */
+ if (RecoveryInProgress())
+ {
+ GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
+ GUC_check_errmsg("cannot set transaction read-write mode during recovery");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * SET TRANSACTION ISOLATION LEVEL
+ *
+ * We allow idempotent changes at any time, but otherwise this can only be
+ * changed in a toplevel transaction that has not yet taken a snapshot.
+ *
+ * As in check_transaction_read_only, allow it if not inside a transaction.
+ */
+bool
+check_XactIsoLevel(int *newval, void **extra, GucSource source)
+{
+ int newXactIsoLevel = *newval;
+
+ if (newXactIsoLevel != XactIsoLevel && IsTransactionState())
+ {
+ if (FirstSnapshotSet)
+ {
+ GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
+ GUC_check_errmsg("SET TRANSACTION ISOLATION LEVEL must be called before any query");
+ return false;
+ }
+ /* We ignore a subtransaction setting it to the existing value. */
+ if (IsSubTransaction())
+ {
+ GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
+ GUC_check_errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction");
+ return false;
+ }
+ /* Can't go to serializable mode while recovery is still active */
+ if (newXactIsoLevel == XACT_SERIALIZABLE && RecoveryInProgress())
+ {
+ GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
+ GUC_check_errmsg("cannot use serializable mode in a hot standby");
+ GUC_check_errhint("You can use REPEATABLE READ instead.");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * SET TRANSACTION [NOT] DEFERRABLE
+ */
+
+bool
+check_transaction_deferrable(bool *newval, void **extra, GucSource source)
+{
+ if (IsSubTransaction())
+ {
+ GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
+ GUC_check_errmsg("SET TRANSACTION [NOT] DEFERRABLE cannot be called within a subtransaction");
+ return false;
+ }
+ if (FirstSnapshotSet)
+ {
+ GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION);
+ GUC_check_errmsg("SET TRANSACTION [NOT] DEFERRABLE must be called before any query");
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Random number seed
+ *
+ * We can't roll back the random sequence on error, and we don't want
+ * config file reloads to affect it, so we only want interactive SET SEED
+ * commands to set it. We use the "extra" storage to ensure that rollbacks
+ * don't try to do the operation again.
+ */
+
+bool
+check_random_seed(double *newval, void **extra, GucSource source)
+{
+ *extra = malloc(sizeof(int));
+ if (!*extra)
+ return false;
+ /* Arm the assign only if source of value is an interactive SET */
+ *((int *) *extra) = (source >= PGC_S_INTERACTIVE);
+
+ return true;
+}
+
+void
+assign_random_seed(double newval, void *extra)
+{
+ /* We'll do this at most once for any setting of the GUC variable */
+ if (*((int *) extra))
+ DirectFunctionCall1(setseed, Float8GetDatum(newval));
+ *((int *) extra) = 0;
+}
+
+const char *
+show_random_seed(void)
+{
+ return "unavailable";
+}
+
+
+/*
+ * SET CLIENT_ENCODING
+ */
+
+bool
+check_client_encoding(char **newval, void **extra, GucSource source)
+{
+ int encoding;
+ const char *canonical_name;
+
+ /* Look up the encoding by name */
+ encoding = pg_valid_client_encoding(*newval);
+ if (encoding < 0)
+ return false;
+
+ /* Get the canonical name (no aliases, uniform case) */
+ canonical_name = pg_encoding_to_char(encoding);
+
+ /*
+ * If we are not within a transaction then PrepareClientEncoding will not
+ * be able to look up the necessary conversion procs. If we are still
+ * starting up, it will return "OK" anyway, and InitializeClientEncoding
+ * will fix things once initialization is far enough along. After
+ * startup, we'll fail. This would only happen if someone tries to change
+ * client_encoding in postgresql.conf and then SIGHUP existing sessions.
+ * It seems like a bad idea for client_encoding to change that way anyhow,
+ * so we don't go out of our way to support it.
+ *
+ * Note: in the postmaster, or any other process that never calls
+ * InitializeClientEncoding, PrepareClientEncoding will always succeed,
+ * and so will SetClientEncoding; but they won't do anything, which is OK.
+ */
+ if (PrepareClientEncoding(encoding) < 0)
+ {
+ if (IsTransactionState())
+ {
+ /* Must be a genuine no-such-conversion problem */
+ GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
+ GUC_check_errdetail("Conversion between %s and %s is not supported.",
+ canonical_name,
+ GetDatabaseEncodingName());
+ }
+ else
+ {
+ /* Provide a useful complaint */
+ GUC_check_errdetail("Cannot change \"client_encoding\" now.");
+ }
+ return false;
+ }
+
+ /*
+ * Replace the user-supplied string with the encoding's canonical name.
+ * This gets rid of aliases and case-folding variations.
+ *
+ * XXX Although canonicalizing seems like a good idea in the abstract, it
+ * breaks pre-9.1 JDBC drivers, which expect that if they send "UNICODE"
+ * as the client_encoding setting then it will read back the same way. As
+ * a workaround, don't replace the string if it's "UNICODE". Remove that
+ * hack when pre-9.1 JDBC drivers are no longer in use.
+ */
+ if (strcmp(*newval, canonical_name) != 0 &&
+ strcmp(*newval, "UNICODE") != 0)
+ {
+ free(*newval);
+ *newval = strdup(canonical_name);
+ if (!*newval)
+ return false;
+ }
+
+ /*
+ * Save the encoding's ID in *extra, for use by assign_client_encoding.
+ */
+ *extra = malloc(sizeof(int));
+ if (!*extra)
+ return false;
+ *((int *) *extra) = encoding;
+
+ return true;
+}
+
+void
+assign_client_encoding(const char *newval, void *extra)
+{
+ int encoding = *((int *) extra);
+
+ /*
+ * Parallel workers send data to the leader, not the client. They always
+ * send data using the database encoding.
+ */
+ if (IsParallelWorker())
+ {
+ /*
+ * During parallel worker startup, we want to accept the leader's
+ * client_encoding setting so that anyone who looks at the value in
+ * the worker sees the same value that they would see in the leader.
+ */
+ if (InitializingParallelWorker)
+ return;
+
+ /*
+ * A change other than during startup, for example due to a SET clause
+ * attached to a function definition, should be rejected, as there is
+ * nothing we can do inside the worker to make it take effect.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+ errmsg("cannot change client_encoding during a parallel operation")));
+ }
+
+ /* We do not expect an error if PrepareClientEncoding succeeded */
+ if (SetClientEncoding(encoding) < 0)
+ elog(LOG, "SetClientEncoding(%d) failed", encoding);
+}
+
+
+/*
+ * SET SESSION AUTHORIZATION
+ */
+
+typedef struct
+{
+ /* This is the "extra" state for both SESSION AUTHORIZATION and ROLE */
+ Oid roleid;
+ bool is_superuser;
+} role_auth_extra;
+
+bool
+check_session_authorization(char **newval, void **extra, GucSource source)
+{
+ HeapTuple roleTup;
+ Form_pg_authid roleform;
+ Oid roleid;
+ bool is_superuser;
+ role_auth_extra *myextra;
+
+ /* Do nothing for the boot_val default of NULL */
+ if (*newval == NULL)
+ return true;
+
+ if (!IsTransactionState())
+ {
+ /*
+ * Can't do catalog lookups, so fail. The result of this is that
+ * session_authorization cannot be set in postgresql.conf, which seems
+ * like a good thing anyway, so we don't work hard to avoid it.
+ */
+ return false;
+ }
+
+ /* Look up the username */
+ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval));
+ if (!HeapTupleIsValid(roleTup))
+ {
+ /*
+ * When source == PGC_S_TEST, we don't throw a hard error for a
+ * nonexistent user name, only a NOTICE. See comments in guc.h.
+ */
+ if (source == PGC_S_TEST)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("role \"%s\" does not exist", *newval)));
+ return true;
+ }
+ GUC_check_errmsg("role \"%s\" does not exist", *newval);
+ return false;
+ }
+
+ roleform = (Form_pg_authid) GETSTRUCT(roleTup);
+ roleid = roleform->oid;
+ is_superuser = roleform->rolsuper;
+
+ ReleaseSysCache(roleTup);
+
+ /* Set up "extra" struct for assign_session_authorization to use */
+ myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra));
+ if (!myextra)
+ return false;
+ myextra->roleid = roleid;
+ myextra->is_superuser = is_superuser;
+ *extra = (void *) myextra;
+
+ return true;
+}
+
+void
+assign_session_authorization(const char *newval, void *extra)
+{
+ role_auth_extra *myextra = (role_auth_extra *) extra;
+
+ /* Do nothing for the boot_val default of NULL */
+ if (!myextra)
+ return;
+
+ SetSessionAuthorization(myextra->roleid, myextra->is_superuser);
+}
+
+
+/*
+ * SET ROLE
+ *
+ * The SQL spec requires "SET ROLE NONE" to unset the role, so we hardwire
+ * a translation of "none" to InvalidOid. Otherwise this is much like
+ * SET SESSION AUTHORIZATION.
+ */
+extern char *role_string; /* in guc.c */
+
+bool
+check_role(char **newval, void **extra, GucSource source)
+{
+ HeapTuple roleTup;
+ Oid roleid;
+ bool is_superuser;
+ role_auth_extra *myextra;
+ Form_pg_authid roleform;
+
+ if (strcmp(*newval, "none") == 0)
+ {
+ /* hardwired translation */
+ roleid = InvalidOid;
+ is_superuser = false;
+ }
+ else
+ {
+ if (!IsTransactionState())
+ {
+ /*
+ * Can't do catalog lookups, so fail. The result of this is that
+ * role cannot be set in postgresql.conf, which seems like a good
+ * thing anyway, so we don't work hard to avoid it.
+ */
+ return false;
+ }
+
+ /*
+ * When source == PGC_S_TEST, we don't throw a hard error for a
+ * nonexistent user name or insufficient privileges, only a NOTICE.
+ * See comments in guc.h.
+ */
+
+ /* Look up the username */
+ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval));
+ if (!HeapTupleIsValid(roleTup))
+ {
+ if (source == PGC_S_TEST)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("role \"%s\" does not exist", *newval)));
+ return true;
+ }
+ GUC_check_errmsg("role \"%s\" does not exist", *newval);
+ return false;
+ }
+
+ roleform = (Form_pg_authid) GETSTRUCT(roleTup);
+ roleid = roleform->oid;
+ is_superuser = roleform->rolsuper;
+
+ ReleaseSysCache(roleTup);
+
+ /*
+ * Verify that session user is allowed to become this role, but skip
+ * this in parallel mode, where we must blindly recreate the parallel
+ * leader's state.
+ */
+ if (!InitializingParallelWorker &&
+ !is_member_of_role(GetSessionUserId(), roleid))
+ {
+ if (source == PGC_S_TEST)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission will be denied to set role \"%s\"",
+ *newval)));
+ return true;
+ }
+ GUC_check_errcode(ERRCODE_INSUFFICIENT_PRIVILEGE);
+ GUC_check_errmsg("permission denied to set role \"%s\"",
+ *newval);
+ return false;
+ }
+ }
+
+ /* Set up "extra" struct for assign_role to use */
+ myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra));
+ if (!myextra)
+ return false;
+ myextra->roleid = roleid;
+ myextra->is_superuser = is_superuser;
+ *extra = (void *) myextra;
+
+ return true;
+}
+
+void
+assign_role(const char *newval, void *extra)
+{
+ role_auth_extra *myextra = (role_auth_extra *) extra;
+
+ SetCurrentRoleId(myextra->roleid, myextra->is_superuser);
+}
+
+const char *
+show_role(void)
+{
+ /*
+ * Check whether SET ROLE is active; if not 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 (!OidIsValid(GetCurrentRoleId()))
+ return "none";
+
+ /* Otherwise we can just use the GUC string */
+ return role_string ? role_string : "none";
+}