public inbox for [email protected]
help / color / mirror / Atom feedRe: [PoC/RFC] Multiple passwords, interval expirations
36+ messages / 9 participants
[nested] [flat]
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2022-03-02 18:02 Joshua Brindle <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Joshua Brindle @ 2022-03-02 18:02 UTC (permalink / raw)
To: pgsql-hackers; +Cc: Stephen Frost <[email protected]>
On Wed, Mar 2, 2022 at 10:35 AM Joshua Brindle
<[email protected]> wrote:
>
> On Wed, Mar 2, 2022 at 9:58 AM Joshua Brindle
> <[email protected]> wrote:
> >
> > This is not intended for PG15.
> >
> > Attached are a proof of concept patchset to implement multiple valid
> > passwords, which have independent expirations, set by a GUC or SQL
> > using an interval.
> >
>
> <snip>
>
> > postgres=# select * from pg_auth_password ;
> > roleid | name |
> > password
> > | expiration
> > --------+---------+-------------------------------------------------------------------------------------------------------------------
> > --------------------+-------------------------------
> > 10 | __def__ |
> > SCRAM-SHA-256$4096:yGiHIYPwc2az7xj/7TIyTA==$OQL/AEcEY1yOCNbrZEj4zDvNnOLpIqltOW1uQvosLvc=:9VRRppuIkSrwhiBN5ePy8wB1y
> > zDa/2uX0WUx6gXi93E= |
> > 16384 | __def__ |
> > SCRAM-SHA-256$4096:AAAAAAAAAAAAAAAAAAAAAA==$1Ivp4d+vAWxowpuGEn05KR9lxyGOms3yy85k3D7XpBg=:k8xUjU6xrJG17PMGa/Zya6pAE
> > /M7pEDaoIFmWvNIEUg= | 2022-03-02 06:52:31.217193-08
> > 16384 | newone |
> > SCRAM-SHA-256$4096:AAAAAAAAAAAAAAAAAAAAAA==$WK3+41CCGDognSnZrtpHhv00z9LuVUjHR1hWq8T1+iE=:w2C5GuhgiEB7wXqPxYfxBKB+e
> > hm4h6Oeif1uzpPIFVk= | 2022-03-03 06:47:53.728159-08
> > (3 rows)
>
> There's obviously a salt problem here that I'll need to fix that
> apparently snuck in at the last rebase, but this brings up one aspect
> of the patchset I didn't mention in the original email:
>
Attached are fixed patches rebased against the lastest master.
> For the SCRAM protocol to work as is with existing clients the salt
> for each password must be the same. Right now ALTER USER will find and
> reuse the salt, but a user passing in a pre-computed SCRAM secret
> currently has no way to get the salt.
>
> for \password (we'll need a new one that takes a password name) I was
> thinking libpq could hold onto the salt that was used to log in, but
> for outside computation we'll need some way for the client to request
> it.
>
> None of that is done yet.
Attachments:
[application/octet-stream] v1-0003-Per-password-expiration.patch (18.9K, ../../CAGB+Vh65WkmR1V5qVUerxmsJE9uBFqwd0oWTTNU=dca_ep2N5Q@mail.gmail.com/2-v1-0003-Per-password-expiration.patch)
download | inline diff:
From 332ea5a58dcd6be7546e350eac1741b620d8e3cb Mon Sep 17 00:00:00 2001
From: Joshua Brindle <[email protected]>
Date: Wed, 2 Mar 2022 06:07:40 -0800
Subject: [PATCH 3/3] Per-password expiration
To build on the multi-password support this
adds per-password expiration, either passed in via ALTER ROLE/CREATE
ROLE with the grammar EXPIRES IN, or via a system-wide setting
called password_valid_duration
Signed-off-by: Joshua Brindle <[email protected]>
---
src/backend/commands/user.c | 121 ++++++++++++++++++++++++-
src/backend/commands/variable.c | 105 +++++++++++++++++++++
src/backend/libpq/auth.c | 9 +-
src/backend/libpq/crypt.c | 38 +++++++-
src/backend/parser/gram.y | 10 +-
src/backend/utils/misc/guc.c | 13 +++
src/include/catalog/pg_auth_password.h | 1 +
src/include/commands/user.h | 3 +
src/include/commands/variable.h | 4 +
src/include/parser/kwlist.h | 1 +
10 files changed, 292 insertions(+), 13 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 384816444da..2e3f4c04418 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -48,8 +48,9 @@
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
-/* GUC parameter */
+/* GUC parameters */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
+Interval *default_password_duration = NULL;
/* default password name */
const char* default_passname = "__def__";
@@ -167,6 +168,84 @@ validate_and_get_salt(char *rolename, char **salt, const char **logdetail)
return true;
}
+static
+Datum
+expires_in_datum(DefElem *passExpiresIn)
+{
+ Interval *interval;
+ Node *arg;
+ A_Const *con;
+ TimestampTz now = GetCurrentTimestamp();
+ Datum passExpiresIn_datum;
+ char *dateout;
+
+ if (default_password_duration != NULL)
+ {
+ /* The default duration GUC is set, use it if nothing came from SQL
+ * or if something came from SQL, reject it if not from a superuser
+ */
+
+ if (passExpiresIn != NULL)
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to override password_validity_duration GUC")));
+ else
+ goto bypass;
+ else
+ {
+ passExpiresIn_datum = DirectFunctionCall2(timestamptz_pl_interval,
+ TimestampGetDatum(now),
+ PointerGetDatum(default_password_duration));
+
+ dateout =
+ DatumGetCString(DirectFunctionCall1(timestamp_out,
+ passExpiresIn_datum));
+
+ ereport(NOTICE,
+ (errmsg("Password will expire at: \"%s\" (from GUC)", dateout)));
+
+ return passExpiresIn_datum;
+ }
+ }
+
+ if (passExpiresIn == NULL) /* No duration requested via SQL and no system default, no expiration */
+ return PointerGetDatum(NULL);
+
+bypass:
+ arg = (Node *)passExpiresIn->arg;
+ if (IsA(arg, TypeCast))
+ {
+ TypeCast *tc = (TypeCast *) passExpiresIn->arg;
+ arg = tc->arg;
+ }
+
+ if (!IsA(arg, A_Const))
+ {
+ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg));
+ return PointerGetDatum(NULL);
+ }
+ con = (A_Const *) arg;
+
+ interval = DatumGetIntervalP(DirectFunctionCall3(interval_in,
+ CStringGetDatum(strVal(&con->val)),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1)));
+
+ passExpiresIn_datum = DirectFunctionCall2(timestamptz_pl_interval,
+ TimestampGetDatum(now),
+ PointerGetDatum(interval));
+
+ dateout =
+ DatumGetCString(DirectFunctionCall1(timestamp_out,
+ passExpiresIn_datum));
+
+ ereport(NOTICE,
+ (errmsg("Password will expire at: \"%s\" (from SQL)", dateout)));
+
+ return passExpiresIn_datum;
+}
+
/*
* CREATE ROLE
*/
@@ -211,7 +290,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
DefElem *dpassName = NULL;
-
+ DefElem *dpassExpiresIn = NULL;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -320,6 +399,12 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dpassName = defel;
}
+ else if (strcmp(defel->defname, "expiresin") == 0)
+ {
+ if (dpassExpiresIn)
+ errorConflictingDefElem(defel, pstate);
+ dpassExpiresIn = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
@@ -508,6 +593,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
Relation pg_auth_password_rel;
TupleDesc pg_auth_password_dsc;
HeapTuple new_tuple;
+ Datum passExpiresIn_datum;
/*
* Don't allow an empty password. Libpq treats an empty password the
@@ -529,19 +615,27 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
}
else
{
+ MemSet(new_password_record, 0, sizeof(new_password_record));
+ MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
+
+ passExpiresIn_datum = expires_in_datum(dpassExpiresIn);
+ if (passExpiresIn_datum != PointerGetDatum(NULL))
+ new_password_record[Anum_pg_auth_password_expiration - 1] = passExpiresIn_datum;
+ else
+ new_password_record_nulls[Anum_pg_auth_password_expiration - 1] = true;
+
/* Encrypt the password to the requested format. */
validate_and_get_salt(stmt->role, &salt, &logdetail);
shadow_pass = encrypt_password(Password_encryption, salt,
password);
- MemSet(new_password_record, 0, sizeof(new_password_record));
- MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
if (passname != NULL)
new_password_record[Anum_pg_auth_password_name - 1] =
DirectFunctionCall1(namein, CStringGetDatum(passname));
else
new_password_record[Anum_pg_auth_password_name - 1] =
DirectFunctionCall1(namein, CStringGetDatum(default_passname));
+
/* open password table and insert it. */
pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
@@ -648,6 +742,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
+ Datum passExpiresIn_datum; /* Time period until password expires */
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
@@ -660,6 +755,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
DefElem *dpassName = NULL;
+ DefElem *dpassExpiresIn = NULL;
Oid roleid;
check_rolespec_name(stmt->role,
@@ -743,6 +839,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dpassName = defel;
}
+ else if (strcmp(defel->defname, "expiresin") == 0)
+ {
+ if (dpassExpiresIn)
+ errorConflictingDefElem(defel, pstate);
+ dpassExpiresIn = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -763,6 +865,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dpassName)
passname = strVal(dpassName->arg);
+
/*
* Scan the pg_authid relation to be certain the user exists.
*/
@@ -849,6 +952,13 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
MemSet(new_password_record_repl, false, sizeof(new_password_record_repl));
+ passExpiresIn_datum = expires_in_datum(dpassExpiresIn);
+ if (passExpiresIn_datum != PointerGetDatum(NULL)) {
+ new_password_record[Anum_pg_auth_password_expiration - 1] = passExpiresIn_datum;
+ new_password_record_repl[Anum_pg_auth_password_expiration - 1] = true;
+ }
+ else
+ new_password_record_nulls[Anum_pg_auth_password_expiration - 1] = true;
/*
* issuper/createrole/etc
@@ -958,7 +1068,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
- if (new_password_record_repl[Anum_pg_auth_password_password - 1] == true)
+ if (new_password_record_repl[Anum_pg_auth_password_password - 1] == true
+ || new_password_record_repl[Anum_pg_auth_password_expiration - 1] == true)
{
Relation pg_auth_password_rel;
TupleDesc pg_auth_password_dsc;
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index e5ddcda0b4a..ca85a10b80b 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -24,6 +24,7 @@
#include "access/xlog.h"
#include "catalog/pg_authid.h"
#include "commands/variable.h"
+#include "commands/user.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/acl.h"
@@ -933,3 +934,107 @@ show_role(void)
/* Otherwise we can just use the GUC string */
return role_string ? role_string : "none";
}
+
+
+/*
+ * check_password_duration: GUC check_hook for password_duration
+ */
+bool
+check_password_duration(char **newval, void **extra, GucSource source)
+{
+ Interval *new_interval;
+ char *endptr;
+
+ const char *valueptr = *newval;
+ char *val;
+ Interval *interval;
+
+ if (newval == NULL || *newval == NULL) {
+ extra = NULL;
+ return true;
+ }
+
+ elog(NOTICE,"Setting password duration to \"%s\"",
+ *newval);
+
+ while (isspace((unsigned char) *valueptr))
+ valueptr++;
+ if (*valueptr != '\'') {
+ val = pstrdup(valueptr);
+ }
+ else
+ {
+ valueptr++;
+ 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) {
+ return false;
+ }
+
+ new_interval = malloc(sizeof(Interval));
+ memcpy(new_interval, interval, sizeof(Interval));
+ pfree(interval);
+
+ /*
+ * Pass back data for assign_password_validity to use
+ */
+ *extra = malloc(sizeof(Interval *));
+ if (!*extra)
+ return false;
+ *((Interval **) *extra) = new_interval;
+
+ return true;
+}
+
+/*
+ * assign_password_validity: GUC assign_hook for timezone
+ */
+void
+assign_password_duration(const char *newval, void *extra)
+{
+ if (extra == NULL)
+ default_password_duration = NULL;
+ else
+ default_password_duration = *((Interval **) extra);
+}
+
+/*
+ * show_password_validity: GUC show_hook for timezone
+ */
+const char *
+show_password_duration(void)
+{
+ const char *intervalout;
+ if (default_password_duration == NULL) {
+ return "";
+ }
+ intervalout = DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(default_password_duration)));
+
+ if (intervalout != NULL)
+ return intervalout;
+
+ return "";
+}
\ No newline at end of file
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 5569599ab31..641cc92549b 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -855,9 +855,14 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
auth_result = CheckSASLAuth(&pg_be_scram_mech, port, (const char **) passwords, num,
logdetail);
- for (i = 0; i < num; i++)
- pfree(passwords[i]);
+ for (i = 0; i < num; i++) {
+ if (passwords[i] != NULL)
+ pfree(passwords[i]);
+ else
+ ereport(LOG,
+ (errmsg("Password %d was null", i)));
+ }
pfree(passwords);
}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 46b458f7d8a..13e0bf16506 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -40,10 +40,12 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
{
HeapTuple roleTup;
Datum datum;
+ TimestampTz current, vuntil = 0;
+
bool isnull;
char **passwords;
CatCList *passlist;
- int i;
+ int i, j = 0, num_valid_passwords = 0;
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
@@ -51,6 +53,7 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
{
*logdetail = psprintf(_("Role \"%s\" does not exist."),
role);
+ *num = 0;
return NULL; /* no such user */
}
@@ -58,7 +61,6 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
ReleaseSysCache(roleTup);
/* Find any existing password that is not the one being updated to get the salt */
passlist = SearchSysCacheList1(AUTHPASSWORDNAME, datum);
- *num = passlist->n_members;
if (passlist->n_members == 0)
{
@@ -68,15 +70,41 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
return NULL; /* user has no password */
}
- passwords = palloc0(sizeof(char *) * passlist->n_members);
+ current = GetCurrentTimestamp();
+
+ for (i = 0; i < passlist->n_members; i++)
+ {
+ HeapTuple tup = &passlist->members[i]->tuple;
+
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_expiration, &isnull);
+
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ if (isnull || vuntil > current)
+ num_valid_passwords++;
+ }
+ passwords = palloc0(sizeof(char *) * num_valid_passwords);
+ *num = num_valid_passwords;
+
for (i = 0; i < passlist->n_members; i++)
{
HeapTuple tup = &passlist->members[i]->tuple;
datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
- Anum_pg_auth_password_password, &isnull);
- passwords[i] = TextDatumGetCString(datum);
+ Anum_pg_auth_password_expiration, &isnull);
+
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ if (isnull || vuntil > current)
+ {
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_password, &isnull);
+ passwords[j++] = pstrdup(TextDatumGetCString(datum));
+ }
}
ReleaseCatCacheList(passlist);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2a288ff13f9..8e0887618d0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -682,7 +682,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
- EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
+ EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPIRES EXPLAIN EXPRESSION
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
@@ -1137,6 +1137,12 @@ AlterOptRoleElem:
$$ = makeDefElem("passname",
(Node *)makeString($2), @1);
}
+ | EXPIRES IN_P Sconst opt_interval
+ {
+ TypeName *t = SystemTypeName("interval");
+ t->typmods = $4;
+ $$ = makeDefElem("expiresin", (Node *)makeStringConstCast($3, @3, t), @1);
+ }
| VALID FOR Sconst
{
$$ = makeDefElem("validFor", (Node *)makeString($3), @1);
@@ -15766,6 +15772,7 @@ unreserved_keyword:
| EXCLUDING
| EXCLUSIVE
| EXECUTE
+ | EXPIRES
| EXPLAIN
| EXPRESSION
| EXTENSION
@@ -16311,6 +16318,7 @@ bare_label_keyword:
| EXCLUSIVE
| EXECUTE
| EXISTS
+ | EXPIRES
| EXPLAIN
| EXPRESSION
| EXTENSION
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 1e3650184b1..a1295a3070f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -682,6 +682,7 @@ static char *recovery_target_string;
static char *recovery_target_xid_string;
static char *recovery_target_name_string;
static char *recovery_target_lsn_string;
+static char *password_duration_string;
/* should be static, but commands/variable.c needs to get at this */
@@ -4662,6 +4663,17 @@ static struct config_string ConfigureNamesString[] =
check_backtrace_functions, assign_backtrace_functions, NULL
},
+ {
+ {"password_valid_duration", PGC_SUSET, CONN_AUTH_AUTH,
+ gettext_noop("Specifies the default validity duration of new passwords."),
+ NULL,
+ GUC_SUPERUSER_ONLY | GUC_NOT_IN_SAMPLE
+ },
+ &password_duration_string,
+ NULL,
+ check_password_duration, assign_password_duration, show_password_duration
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
@@ -5039,6 +5051,7 @@ static struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
diff --git a/src/include/catalog/pg_auth_password.h b/src/include/catalog/pg_auth_password.h
index 4181caad30b..1cb0a211ee7 100644
--- a/src/include/catalog/pg_auth_password.h
+++ b/src/include/catalog/pg_auth_password.h
@@ -31,6 +31,7 @@ CATALOG(pg_auth_password,4551,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_RO
#ifdef CATALOG_VARLEN /* variable-length fields start here */
text password BKI_FORCE_NOT_NULL; /* password */
+ timestamptz expiration BKI_FORCE_NULL; /* password expiration time, if any */
#endif
} FormData_pg_auth_password;
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 7223b1fdae4..1203358933a 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -19,6 +19,9 @@
/* GUC. Is actually of type PasswordType. */
extern int Password_encryption;
+/* GUC. system-wide password validity duration */
+extern Interval *default_password_duration;
+
/* Hook to check passwords in CreateRole() and AlterRole() */
typedef void (*check_password_hook_type) (const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null);
diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h
index 0e5ddcbcf37..3b941c554cb 100644
--- a/src/include/commands/variable.h
+++ b/src/include/commands/variable.h
@@ -34,5 +34,9 @@ extern void assign_session_authorization(const char *newval, void *extra);
extern bool check_role(char **newval, void **extra, GucSource source);
extern void assign_role(const char *newval, void *extra);
extern const char *show_role(void);
+extern bool check_password_duration(char **newval, void **extra, GucSource source);
+extern void assign_password_duration(const char *newval, void *extra);
+extern const char *show_password_duration(void);
+
#endif /* VARIABLE_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index e7239b34885..0e1c8098363 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -159,6 +159,7 @@ PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("expires", EXPIRES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("expression", EXPRESSION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD, BARE_LABEL)
--
2.31.1
[application/octet-stream] v1-0002-multiple-passwords-work-with-scram-and-md5.patch (40.5K, ../../CAGB+Vh65WkmR1V5qVUerxmsJE9uBFqwd0oWTTNU=dca_ep2N5Q@mail.gmail.com/3-v1-0002-multiple-passwords-work-with-scram-and-md5.patch)
download | inline diff:
From 6f2451e1f620e7e211804792a256ea9735bd0abf Mon Sep 17 00:00:00 2001
From: Joshua Brindle <[email protected]>
Date: Tue, 1 Feb 2022 14:32:55 -0800
Subject: [PATCH 2/3] multiple passwords work with scram and md5
also renaming roles, dropping roles, switching between
password_encryption
Signed-off-by: Joshua Brindle <[email protected]>
---
src/backend/commands/user.c | 199 +++++++++++++++++++++----
src/backend/libpq/auth-sasl.c | 4 +-
src/backend/libpq/auth-scram.c | 184 +++++++++++++----------
src/backend/libpq/auth.c | 94 +++++-------
src/backend/libpq/crypt.c | 50 ++++---
src/backend/parser/gram.y | 13 +-
src/backend/utils/cache/catcache.c | 2 +-
src/backend/utils/cache/syscache.c | 6 +-
src/include/catalog/pg_auth_password.h | 9 +-
src/include/commands/user.h | 2 +-
src/include/libpq/crypt.h | 2 +-
src/include/libpq/sasl.h | 4 +-
src/include/libpq/scram.h | 2 +-
src/include/parser/kwlist.h | 1 +
src/include/utils/syscache.h | 2 +-
src/test/regress/expected/password.out | 2 +-
16 files changed, 373 insertions(+), 203 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 9ff99342eef..384816444da 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -18,6 +18,7 @@
#include "access/xact.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
+
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
@@ -31,11 +32,14 @@
#include "commands/defrem.h"
#include "commands/seclabel.h"
#include "commands/user.h"
+#include "common/scram-common.h"
#include "libpq/crypt.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+#include "utils/catcache.h"
#include "utils/fmgroids.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
@@ -47,6 +51,9 @@ Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
/* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
+/* default password name */
+const char* default_passname = "__def__";
+
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -69,7 +76,7 @@ have_createrole_privilege(void)
* Is the role able to log in
*/
bool
-is_role_valid(const char *rolename, char **logdetail)
+is_role_valid(const char *rolename, const char **logdetail)
{
HeapTuple roleTup;
Datum datum;
@@ -102,6 +109,64 @@ is_role_valid(const char *rolename, char **logdetail)
}
+static
+bool
+validate_and_get_salt(char *rolename, char **salt, const char **logdetail)
+{
+ char **current_secrets;
+ char *salt1, *salt2 = NULL;
+ int i, num;
+ PasswordType passtype;
+
+ if (Password_encryption == PASSWORD_TYPE_MD5)
+ {
+ *salt = rolename; /* md5 always uses role name, no need to look through the passwords */
+ return true;
+ }
+ else if (Password_encryption == PASSWORD_TYPE_PLAINTEXT)
+ {
+ *salt = NULL; /* Plaintext does not have a salt */
+ return true;
+ }
+
+ current_secrets = get_role_passwords(rolename, logdetail, &num);
+ if (num == 0)
+ {
+ *salt = NULL; /* No existing passwords, allow one to be generated */
+ return true;
+ }
+ for (i = 0; i < num; i++) {
+ passtype = get_password_type(current_secrets[i]);
+ if (passtype == PASSWORD_TYPE_MD5 || passtype == PASSWORD_TYPE_PLAINTEXT)
+ continue; /* md5 uses rolename as salt so it is always the same and plaintext has no salt */
+ else if (passtype == PASSWORD_TYPE_SCRAM_SHA_256) {
+ int iterations;
+ uint8 stored_key[SCRAM_KEY_LEN];
+ uint8 server_key[SCRAM_KEY_LEN];
+
+ parse_scram_secret(current_secrets[i], &iterations, &salt1,
+ stored_key, server_key);
+
+ if (salt2 != NULL) {
+ if (strcmp(salt1, salt2)) {
+ *logdetail = psprintf(_("inconsistent salts, clearing password"));
+ *salt = NULL;
+ return false;
+ }
+ }
+ else
+ salt2 = salt1;
+ }
+
+ }
+ for (i = 0; i < num; i++)
+ pfree(current_secrets[i]);
+ pfree(current_secrets);
+ if (salt2)
+ *salt = pstrdup(salt2);
+ return true;
+}
+
/*
* CREATE ROLE
*/
@@ -117,6 +182,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
+ char *passname = NULL; /* name of the password for managing multiple passwords */
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -144,6 +210,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ DefElem *dpassName = NULL;
+
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -246,6 +314,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
+ else if (strcmp(defel->defname, "passname") == 0)
+ {
+ if (dpassName)
+ errorConflictingDefElem(defel, pstate);
+ dpassName = defel;
+ }
+
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -283,6 +358,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
validUntil = strVal(dvalidUntil->arg);
if (dbypassRLS)
bypassrls = boolVal(dbypassRLS->arg);
+ if (dpassName)
+ passname = strVal(dpassName->arg);
/* Check some permissions first */
if (issuper)
@@ -424,8 +501,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- char *shadow_pass;
- char *logdetail;
+ char *shadow_pass, *salt;
+ const char *logdetail;
Datum new_password_record[Natts_pg_auth_password];
bool new_password_record_nulls[Natts_pg_auth_password];
Relation pg_auth_password_rel;
@@ -453,12 +530,18 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
else
{
/* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, stmt->role,
+ validate_and_get_salt(stmt->role, &salt, &logdetail);
+ shadow_pass = encrypt_password(Password_encryption, salt,
password);
MemSet(new_password_record, 0, sizeof(new_password_record));
MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
-
+ if (passname != NULL)
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(passname));
+ else
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(default_passname));
/* open password table and insert it. */
pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
@@ -560,6 +643,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ListCell *option;
char *rolename;
char *password = NULL; /* user password */
+ char *passname = NULL; /* password name for managing multiple passwords */
int connlimit = -1; /* maximum connections allowed */
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
@@ -575,6 +659,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ DefElem *dpassName = NULL;
Oid roleid;
check_rolespec_name(stmt->role,
@@ -652,6 +737,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
+ else if (strcmp(defel->defname, "passname") == 0)
+ {
+ if (dpassName)
+ errorConflictingDefElem(defel, pstate);
+ dpassName = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -669,6 +760,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
}
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
+ if (dpassName)
+ passname = strVal(dpassName->arg);
/*
* Scan the pg_authid relation to be certain the user exists.
@@ -805,8 +898,9 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
/* password */
if (password)
{
- char *shadow_pass;
- const char *logdetail = NULL;
+ char *salt = NULL;
+ const char *logdetail = NULL;
+ char *shadow_pass;
/* Like in CREATE USER, don't allow an empty password. */
if (password[0] == '\0' ||
@@ -818,13 +912,26 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
}
else
{
- /* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, rolename,
- password);
- new_password_record[Anum_pg_auth_password_password - 1] =
- CStringGetTextDatum(shadow_pass);
+ if (!validate_and_get_salt(rolename, &salt, &logdetail))
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
+ else
+ {
+ /* Encrypt the password to the requested format. */
+ shadow_pass = encrypt_password(Password_encryption, salt,
+ password);
+ new_password_record[Anum_pg_auth_password_password - 1] =
+ CStringGetTextDatum(shadow_pass);
+
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
+
+ if (passname)
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(passname));
+ else
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(default_passname));
+ }
}
- new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
}
/* unset password */
@@ -859,7 +966,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
- password_tuple = SearchSysCache1(AUTHPASSWORD, ObjectIdGetDatum(roleid));
+ if (dpassName)
+ password_tuple = SearchSysCache2(AUTHPASSWORDNAME, ObjectIdGetDatum(roleid), CStringGetDatum(passname));
+ else
+ password_tuple = SearchSysCache2(AUTHPASSWORDNAME, ObjectIdGetDatum(roleid), CStringGetDatum(default_passname));
if (new_password_record_nulls[Anum_pg_auth_password_password - 1] == true) /* delete existing password */
{
@@ -1157,14 +1267,23 @@ DropRole(DropRoleStmt *stmt)
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
/*
- * Drop password
+ * Drop password(s)
*/
- tuple = SearchSysCache1(AUTHPASSWORD, roleid);
- if (HeapTupleIsValid(tuple)) {
- CatalogTupleDelete(pg_auth_password_rel, &tuple->t_self);
- ReleaseSysCache(tuple);
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_password_roleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ sscan = systable_beginscan(pg_auth_password_rel, AuthPasswordRoleOidIndexId,
+ true, NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+ {
+ CatalogTupleDelete(pg_auth_password_rel, &tmp_tuple->t_self);
}
+ systable_endscan(sscan);
+
/*
* Remove settings for this role.
*/
@@ -1201,6 +1320,9 @@ RenameRole(const char *oldname, const char *newname)
newtuple,
passtuple;
TupleDesc dsc;
+ ScanKeyData scankey;
+ SysScanDesc sscan;
+
Relation rel;
Datum datum;
bool isnull = true;
@@ -1211,6 +1333,7 @@ RenameRole(const char *oldname, const char *newname)
Oid roleid;
ObjectAddress address;
Form_pg_authid authform;
+ Relation pg_auth_password_rel;
rel = table_open(AuthIdRelationId, RowExclusiveLock);
dsc = RelationGetDescr(rel);
@@ -1301,28 +1424,38 @@ RenameRole(const char *oldname, const char *newname)
CStringGetDatum(newname));
repl_null[Anum_pg_authid_rolname - 1] = false;
- passtuple = SearchSysCache1(AUTHPASSWORD, roleid);
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_password_roleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
- if (HeapTupleIsValid(passtuple))
- datum = SysCacheGetAttr(AUTHPASSWORD, passtuple,
- Anum_pg_auth_password_password, &isnull);
+ sscan = systable_beginscan(pg_auth_password_rel, AuthPasswordRoleOidIndexId,
+ true, NULL, 1, &scankey);
- if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
+ while (HeapTupleIsValid(passtuple = systable_getnext(sscan)))
{
- Relation pg_auth_password_rel;
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, passtuple,
+ Anum_pg_auth_password_password, &isnull);
- /* MD5 uses the username as salt, so just clear it on a rename */
- pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
+ {
- if (HeapTupleIsValid(passtuple)) {
- CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
- ReleaseSysCache(passtuple);
+ /* MD5 uses the username as salt, so just clear it on a rename */
+
+ if (HeapTupleIsValid(passtuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
+ ereport(NOTICE,
+ (errmsg("MD5 password cleared because of role rename")));
+
+ }
}
- table_close(pg_auth_password_rel, NoLock);
- ereport(NOTICE,
- (errmsg("MD5 password cleared because of role rename")));
}
+ systable_endscan(sscan);
+ table_close(pg_auth_password_rel, NoLock);
+
newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
CatalogTupleUpdate(rel, &oldtuple->t_self, newtuple);
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index 805b3695b78..652c18fa94a 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -49,7 +49,7 @@
* should just pass NULL.
*/
int
-CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
+CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, const char **passwords, int num,
const char **logdetail)
{
StringInfoData sasl_mechs;
@@ -136,7 +136,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
* This is because we don't want to reveal to an attacker what
* usernames are valid, nor which users have a valid password.
*/
- opaq = mech->init(port, selected_mech, shadow_pass);
+ opaq = mech->init(port, selected_mech, passwords, num);
inputlen = pq_getmsgint(&buf, 4);
if (inputlen == -1)
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 795f1cba555..95db4aaf958 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -109,7 +109,7 @@
static void scram_get_mechanisms(Port *port, StringInfo buf);
static void *scram_init(Port *port, const char *selected_mech,
- const char *shadow_pass);
+ const char **secrets, const int num_secrets);
static int scram_exchange(void *opaq, const char *input, int inputlen,
char **output, int *outputlen,
const char **logdetail);
@@ -132,6 +132,13 @@ typedef enum
SCRAM_AUTH_FINISHED
} scram_state_enum;
+typedef struct
+{
+ uint8 StoredKey[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+} scram_secret;
+
+
typedef struct
{
scram_state_enum state;
@@ -141,10 +148,16 @@ typedef struct
Port *port;
bool channel_binding_in_use;
+ /*
+ * The salt and iterations must be the same for all
+ * secrets since they are sent as part of the initial message
+ */
int iterations;
char *salt; /* base64-encoded */
- uint8 StoredKey[SCRAM_KEY_LEN];
- uint8 ServerKey[SCRAM_KEY_LEN];
+ /* Array of possible secrets */
+ scram_secret *secrets;
+ int num_secrets;
+ int chosen_secret; /* secret chosen during final client message */
/* Fields of the first message from client */
char cbind_flag;
@@ -220,17 +233,20 @@ scram_get_mechanisms(Port *port, StringInfo buf)
* It should be one of the mechanisms that we support, as returned by
* scram_get_mechanisms().
*
- * 'shadow_pass' is the role's stored secret, from pg_auth_password.password.
+ * 'passwords' are the role's stored secrets, from pg_auth_password.password.
* The username was provided by the client in the startup message, and is
* available in port->user_name. If 'shadow_pass' is NULL, we still perform
* an authentication exchange, but it will fail, as if an incorrect password
* was given.
*/
static void *
-scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
+scram_init(Port *port, const char *selected_mech, const char **secrets, const int num_secrets)
{
scram_state *state;
- bool got_secret;
+ bool got_secret = false;
+ int i;
+ int iterations;
+ char *salt = NULL; /* base64-encoded */
state = (scram_state *) palloc0(sizeof(scram_state));
state->port = port;
@@ -260,46 +276,48 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
/*
* Parse the stored secret.
*/
- if (shadow_pass)
+ if (secrets)
{
- int password_type = get_password_type(shadow_pass);
-
- if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
+ state->secrets = palloc0(sizeof(scram_secret) * num_secrets);
+ state->num_secrets = num_secrets;
+ for (i = 0; i < num_secrets; i++)
{
- if (parse_scram_secret(shadow_pass, &state->iterations, &state->salt,
- state->StoredKey, state->ServerKey))
- got_secret = true;
- else
+ int password_type = get_password_type(secrets[i]);
+
+ if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
{
- /*
- * The password looked like a SCRAM secret, but could not be
- * parsed.
- */
- ereport(LOG,
- (errmsg("invalid SCRAM secret for user \"%s\"",
- state->port->user_name)));
- got_secret = false;
+
+ if (parse_scram_secret(secrets[i], &state->iterations, &state->salt,
+ state->secrets[i].StoredKey, state->secrets[i].ServerKey))
+ {
+ if (salt) {
+ /* The stored iterations and salt must match or we cannot proceed, allow failure via mock */
+ if (strcmp(salt, state->salt) || iterations != state->iterations) {
+ ereport(WARNING, (errmsg("inconsistent salt or iterations for user \"%s\"",
+ state->port->user_name)));
+ got_secret = false; /* fail and allow mock creditials to be created */
+ break;
+ }
+ }
+ else
+ {
+ salt = state->salt;
+ iterations = state->iterations;
+ got_secret = true; /* We got at least one good SCRAM secret */
+ }
+ }
+ else
+ {
+ /*
+ * The password looked like a SCRAM secret, but could not be
+ * parsed.
+ */
+ ereport(LOG,
+ (errmsg("invalid SCRAM secret for user \"%s\"",
+ state->port->user_name)));
+ }
}
}
- else
- {
- /*
- * The user doesn't have SCRAM secret. (You cannot do SCRAM
- * authentication with an MD5 hash.)
- */
- state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM secret."),
- state->port->user_name);
- got_secret = false;
- }
- }
- else
- {
- /*
- * The caller requested us to perform a dummy authentication. This is
- * considered normal, since the caller requested it, so don't set log
- * detail.
- */
- got_secret = false;
}
/*
@@ -310,8 +328,10 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
*/
if (!got_secret)
{
+ state->secrets = palloc0(sizeof(scram_secret));
+
mock_scram_secret(state->port->user_name, &state->iterations,
- &state->salt, state->StoredKey, state->ServerKey);
+ &state->salt, state->secrets[0].StoredKey, state->secrets[0].ServerKey);
state->doomed = true;
}
@@ -459,7 +479,7 @@ scram_exchange(void *opaq, const char *input, int inputlen,
* The result is palloc'd, so caller is responsible for freeing it.
*/
char *
-pg_be_scram_build_secret(const char *password)
+pg_be_scram_build_secret(const char *password, const char *salt)
{
char *prep_password;
pg_saslprep_rc rc;
@@ -476,12 +496,14 @@ pg_be_scram_build_secret(const char *password)
if (rc == SASLPREP_SUCCESS)
password = (const char *) prep_password;
- /* Generate random salt */
- if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
+ /* Use passed in salt or generate random salt */
+ if (!salt && !pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate random salt")));
-
+ else if (salt)
+ pg_b64_decode(salt, strlen(salt), saltbuf, SCRAM_DEFAULT_SALT_LEN);
+
result = scram_build_secret(saltbuf, SCRAM_DEFAULT_SALT_LEN,
SCRAM_DEFAULT_ITERATIONS, password,
&errstr);
@@ -1114,47 +1136,57 @@ verify_client_proof(scram_state *state)
uint8 ClientSignature[SCRAM_KEY_LEN];
uint8 ClientKey[SCRAM_KEY_LEN];
uint8 client_StoredKey[SCRAM_KEY_LEN];
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
- int i;
+ pg_hmac_ctx *ctx;
+ int i, j;
const char *errstr = NULL;
-
/*
* Calculate ClientSignature. Note that we don't log directly a failure
* here even when processing the calculations as this could involve a mock
* authentication.
*/
- if (pg_hmac_init(ctx, state->StoredKey, SCRAM_KEY_LEN) < 0 ||
- pg_hmac_update(ctx,
- (uint8 *) state->client_first_message_bare,
- strlen(state->client_first_message_bare)) < 0 ||
- pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
- pg_hmac_update(ctx,
- (uint8 *) state->server_first_message,
- strlen(state->server_first_message)) < 0 ||
- pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
- pg_hmac_update(ctx,
- (uint8 *) state->client_final_message_without_proof,
- strlen(state->client_final_message_without_proof)) < 0 ||
- pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+ for (j = 0; j < state->num_secrets; j++)
{
- elog(ERROR, "could not calculate client signature: %s",
- pg_hmac_error(ctx));
- }
+ ctx = pg_hmac_create(PG_SHA256);
+ elog(LOG, "Trying to verify password %d", j);
+
+ if (pg_hmac_init(ctx, state->secrets[j].StoredKey, SCRAM_KEY_LEN) < 0 ||
+ pg_hmac_update(ctx,
+ (uint8 *) state->client_first_message_bare,
+ strlen(state->client_first_message_bare)) < 0 ||
+ pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
+ pg_hmac_update(ctx,
+ (uint8 *) state->server_first_message,
+ strlen(state->server_first_message)) < 0 ||
+ pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
+ pg_hmac_update(ctx,
+ (uint8 *) state->client_final_message_without_proof,
+ strlen(state->client_final_message_without_proof)) < 0 ||
+ pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+ {
+ elog(LOG, "could not calculate client signature");
+ pg_hmac_free(ctx);
+ continue;
+ }
- pg_hmac_free(ctx);
+ elog(LOG, "success on %d", j);
- /* Extract the ClientKey that the client calculated from the proof */
- for (i = 0; i < SCRAM_KEY_LEN; i++)
- ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+ pg_hmac_free(ctx);
- /* Hash it one more time, and compare with StoredKey */
- if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
- elog(ERROR, "could not hash stored key: %s", errstr);
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
- if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
- return false;
+ /* Hash it one more time, and compare with StoredKey */
+ if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
+ elog(ERROR, "could not hash stored key: %s", errstr);
- return true;
+ if (memcmp(client_StoredKey, state->secrets[j].StoredKey, SCRAM_KEY_LEN) == 0) {
+ elog(LOG, "Moving forward with Password %d", j);
+ state->chosen_secret = j;
+ return true;
+ }
+ }
+
+ return false;
}
/*
@@ -1380,7 +1412,7 @@ build_server_final_message(scram_state *state)
pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
/* calculate ServerSignature */
- if (pg_hmac_init(ctx, state->ServerKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, state->secrets[state->chosen_secret].ServerKey, SCRAM_KEY_LEN) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 5cfde1eaa13..5569599ab31 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -58,8 +58,7 @@ static void set_authn_id(Port *port, const char *id);
static int CheckPasswordAuth(Port *port, const char **logdetail);
static int CheckPWChallengeAuth(Port *port, const char **logdetail);
-static int CheckMD5Auth(Port *port, char *shadow_pass,
- const char **logdetail);
+static int CheckMD5Auth(Port *port, const char **passwords, int num, const char **logdetail);
/*----------------------------------------------------------------
@@ -790,8 +789,9 @@ static int
CheckPasswordAuth(Port *port, const char **logdetail)
{
char *passwd;
- int result;
- char *shadow_pass;
+ int result = STATUS_ERROR;
+ int i, num;
+ char **passwords;
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
@@ -799,17 +799,21 @@ CheckPasswordAuth(Port *port, const char **logdetail)
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
- shadow_pass = get_role_password(port->user_name, logdetail);
- if (shadow_pass)
- {
- result = plain_crypt_verify(port->user_name, shadow_pass, passwd,
- logdetail);
- }
- else
- result = STATUS_ERROR;
+ passwords = get_role_passwords(port->user_name, logdetail, &num);
+ if (passwords != NULL) {
+ for (i = 0; i < num; i++)
+ {
+ result = plain_crypt_verify(port->user_name, passwords[i], passwd,
+ logdetail);
+ if (result == STATUS_OK)
+ break; /* Found a matching password, no need to try any others */
+ }
+ for (i = 0; passwords[i] != NULL; i++)
+ pfree(passwords[i]);
- if (shadow_pass)
- pfree(shadow_pass);
+ pfree(passwords);
+ }
+
pfree(passwd);
if (result == STATUS_OK)
@@ -824,29 +828,15 @@ CheckPasswordAuth(Port *port, const char **logdetail)
static int
CheckPWChallengeAuth(Port *port, const char **logdetail)
{
- int auth_result;
- char *shadow_pass;
- PasswordType pwtype;
+ int auth_result = STATUS_ERROR;
+ int i, num;
+ char **passwords;
Assert(port->hba->auth_method == uaSCRAM ||
port->hba->auth_method == uaMD5);
- /* First look up the user's password. */
- shadow_pass = get_role_password(port->user_name, logdetail);
-
- /*
- * If the user does not exist, or has no password or it's expired, we
- * still go through the motions of authentication, to avoid revealing to
- * the client that the user didn't exist. If 'md5' is allowed, we choose
- * whether to use 'md5' or 'scram-sha-256' authentication based on current
- * password_encryption setting. The idea is that most genuine users
- * probably have a password of that type, and if we pretend that this user
- * had a password of that type, too, it "blends in" best.
- */
- if (!shadow_pass)
- pwtype = Password_encryption;
- else
- pwtype = get_password_type(shadow_pass);
+ /* First look up the user's passwords. */
+ passwords = get_role_passwords(port->user_name, logdetail, &num);
/*
* If 'md5' authentication is allowed, decide whether to perform 'md5' or
@@ -858,23 +848,17 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
* had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will
* fail.
*/
- if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5)
- auth_result = CheckMD5Auth(port, shadow_pass, logdetail);
- else
- auth_result = CheckSASLAuth(&pg_be_scram_mech, port, shadow_pass,
- logdetail);
+ if (passwords != NULL) {
+ if (port->hba->auth_method == uaMD5)
+ auth_result = CheckMD5Auth(port, (const char **) passwords, num, logdetail);
+ else
+ auth_result = CheckSASLAuth(&pg_be_scram_mech, port, (const char **) passwords, num,
+ logdetail);
- if (shadow_pass)
- pfree(shadow_pass);
+ for (i = 0; i < num; i++)
+ pfree(passwords[i]);
- /*
- * If get_role_password() returned error, return error, even if the
- * authentication succeeded.
- */
- if (!shadow_pass)
- {
- Assert(auth_result != STATUS_OK);
- return STATUS_ERROR;
+ pfree(passwords);
}
if (auth_result == STATUS_OK)
@@ -884,11 +868,12 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
}
static int
-CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
+CheckMD5Auth(Port *port, const char **passwords, int num, const char **logdetail)
{
char md5Salt[4]; /* Password salt */
char *passwd;
int result;
+ int i;
if (Db_user_namespace)
ereport(FATAL,
@@ -909,12 +894,13 @@ CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
- if (shadow_pass)
- result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
+ for (i = 0; i < num; i++)
+ {
+ result = md5_crypt_verify(port->user_name, passwords[i], passwd,
md5Salt, 4, logdetail);
- else
- result = STATUS_ERROR;
-
+ if (result == STATUS_OK)
+ break;
+ }
pfree(passwd);
return result;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 745e61034c2..46b458f7d8a 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -23,6 +23,7 @@
#include "libpq/scram.h"
#include "miscadmin.h"
#include "utils/builtins.h"
+#include "utils/catcache.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
@@ -34,13 +35,15 @@
* for the postmaster log, in *logdetail. The error reason should *not* be
* sent to the client, to avoid giving away user information!
*/
-char *
-get_role_password(const char *role, const char **logdetail)
+char **
+get_role_passwords(const char *role, const char **logdetail, int *num)
{
- HeapTuple roleTup, passTup;
+ HeapTuple roleTup;
Datum datum;
bool isnull;
- char *shadow_pass;
+ char **passwords;
+ CatCList *passlist;
+ int i;
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
@@ -52,30 +55,33 @@ get_role_password(const char *role, const char **logdetail)
}
datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_oid, &isnull);
-
- passTup = SearchSysCache1(AUTHPASSWORD, datum);
+ ReleaseSysCache(roleTup);
+ /* Find any existing password that is not the one being updated to get the salt */
+ passlist = SearchSysCacheList1(AUTHPASSWORDNAME, datum);
+ *num = passlist->n_members;
- if (!HeapTupleIsValid(passTup))
+ if (passlist->n_members == 0)
{
*logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
+ ReleaseCatCacheList(passlist);
return NULL; /* user has no password */
}
- datum = SysCacheGetAttr(AUTHPASSWORD, passTup,
- Anum_pg_auth_password_password, &isnull);
- ReleaseSysCache(roleTup);
- if (isnull) /* this should not happen any more but just in case */
+ passwords = palloc0(sizeof(char *) * passlist->n_members);
+
+ for (i = 0; i < passlist->n_members; i++)
{
- ReleaseSysCache(passTup);
- *logdetail = psprintf(_("User \"%s\" has no password assigned."),
- role);
- return NULL; /* user has no password */
+ HeapTuple tup = &passlist->members[i]->tuple;
+
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_password, &isnull);
+ passwords[i] = TextDatumGetCString(datum);
}
- shadow_pass = TextDatumGetCString(datum);
- ReleaseSysCache(passTup);
- return shadow_pass;
+ ReleaseCatCacheList(passlist);
+
+ return passwords;
}
/*
@@ -107,7 +113,7 @@ get_password_type(const char *shadow_pass)
* hash, so it is stored as it is regardless of the requested type.
*/
char *
-encrypt_password(PasswordType target_type, const char *role,
+encrypt_password(PasswordType target_type, const char *salt,
const char *password)
{
PasswordType guessed_type = get_password_type(password);
@@ -128,13 +134,13 @@ encrypt_password(PasswordType target_type, const char *role,
case PASSWORD_TYPE_MD5:
encrypted_password = palloc(MD5_PASSWD_LEN + 1);
- if (!pg_md5_encrypt(password, role, strlen(role),
+ if (!pg_md5_encrypt(password, salt, strlen(salt),
encrypted_password, &errstr))
- elog(ERROR, "password encryption failed: %s", errstr);
+ elog(ERROR, "password encryption failed %s", errstr);
return encrypted_password;
case PASSWORD_TYPE_SCRAM_SHA_256:
- return pg_be_scram_build_secret(password);
+ return pg_be_scram_build_secret(password, salt);
case PASSWORD_TYPE_PLAINTEXT:
elog(ERROR, "cannot encrypt password with 'plaintext'");
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a03b33b53bd..2a288ff13f9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -716,7 +716,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
- PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+ PARALLEL PARSER PARTIAL PARTITION PASSING PASSNAME PASSWORD PLACING PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
@@ -1132,6 +1132,15 @@ AlterOptRoleElem:
errhint("Remove UNENCRYPTED to store the password in encrypted form instead."),
parser_errposition(@1)));
}
+ | PASSNAME Sconst
+ {
+ $$ = makeDefElem("passname",
+ (Node *)makeString($2), @1);
+ }
+ | VALID FOR Sconst
+ {
+ $$ = makeDefElem("validFor", (Node *)makeString($3), @1);
+ }
| INHERIT
{
$$ = makeDefElem("inherit", (Node *)makeBoolean(true), @1);
@@ -15855,6 +15864,7 @@ unreserved_keyword:
| PARTIAL
| PARTITION
| PASSING
+ | PASSNAME
| PASSWORD
| PLANS
| POLICY
@@ -16433,6 +16443,7 @@ bare_label_keyword:
| PARTIAL
| PARTITION
| PASSING
+ | PASSNAME
| PASSWORD
| PLACING
| PLANS
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 356e9cde6fb..c421ac0b19f 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1104,7 +1104,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
- case AUTHPASSWORD:
+ case AUTHPASSWORDNAME:
case AUTHMEMMEMROLE:
case DATABASEOID:
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 49d49f13252..2c39cfb083f 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -255,12 +255,12 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
- {AuthPasswordRelationId, /* AUTHPASSWORD */
+ {AuthPasswordRelationId, /* AUTHPASSWORDNAME */
AuthPasswordRoleOidIndexId,
- 1,
+ 2,
{
Anum_pg_auth_password_roleid,
- 0,
+ Anum_pg_auth_password_name,
0,
0
},
diff --git a/src/include/catalog/pg_auth_password.h b/src/include/catalog/pg_auth_password.h
index beaa2d40b90..4181caad30b 100644
--- a/src/include/catalog/pg_auth_password.h
+++ b/src/include/catalog/pg_auth_password.h
@@ -24,11 +24,13 @@
* typedef struct FormData_pg_auth_password
* ----------------
*/
-CATALOG(pg_auth_password,4548,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4549,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
+CATALOG(pg_auth_password,4551,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4552,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
{
Oid roleid BKI_LOOKUP(pg_authid); /* ID of a role */
+ NameData name; /* name of password for multiple password support */
+
#ifdef CATALOG_VARLEN /* variable-length fields start here */
- text password; /* password */
+ text password BKI_FORCE_NOT_NULL; /* password */
#endif
} FormData_pg_auth_password;
@@ -44,7 +46,6 @@ DECLARE_TOAST(pg_auth_password, 4175, 4176);
typedef FormData_pg_auth_password *Form_pg_auth_password;
-DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4550, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops));
-
+DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4553, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops, name name_ops));
#endif /* PG_AUTH_PASSWORD_H */
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index ce39db5a491..7223b1fdae4 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -24,7 +24,7 @@ typedef void (*check_password_hook_type) (const char *username, const char *shad
extern PGDLLIMPORT check_password_hook_type check_password_hook;
-extern bool is_role_valid(const char *rolename, char **logdetail);
+extern bool is_role_valid(const char *rolename, const char **logdetail);
extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index b8ff8ccb417..84c9c4b4f48 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -35,7 +35,7 @@ extern PasswordType get_password_type(const char *shadow_pass);
extern char *encrypt_password(PasswordType target_type, const char *role,
const char *password);
-extern char *get_role_password(const char *role, const char **logdetail);
+extern char **get_role_passwords(const char *role, const char **logdetail, int *num);
extern int md5_crypt_verify(const char *role, const char *shadow_pass,
const char *client_pass, const char *md5_salt,
diff --git a/src/include/libpq/sasl.h b/src/include/libpq/sasl.h
index 71cc0dc2514..9892f9aad38 100644
--- a/src/include/libpq/sasl.h
+++ b/src/include/libpq/sasl.h
@@ -77,7 +77,7 @@ typedef struct pg_be_sasl_mech
* disclosing valid user names.
*---------
*/
- void *(*init) (Port *port, const char *mech, const char *shadow_pass);
+ void *(*init) (Port *port, const char *mech, const char **secrets, const int num_secrets);
/*---------
* exchange()
@@ -131,6 +131,6 @@ typedef struct pg_be_sasl_mech
/* Common implementation for auth.c */
extern int CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port,
- char *shadow_pass, const char **logdetail);
+ const char **passwords, int num, const char **logdetail);
#endif /* PG_SASL_H */
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index e60992a0d2d..8cc6f492eeb 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -21,7 +21,7 @@
extern const pg_be_sasl_mech pg_be_scram_mech;
/* Routines to handle and check SCRAM-SHA-256 secret */
-extern char *pg_be_scram_build_secret(const char *password);
+extern char *pg_be_scram_build_secret(const char *password, const char *salt);
extern bool parse_scram_secret(const char *secret, int *iterations, char **salt,
uint8 *stored_key, uint8 *server_key);
extern bool scram_verify_plain_password(const char *username,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bcef7eed2f3..e7239b34885 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -311,6 +311,7 @@ PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("passname", PASSNAME, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index ab846e8b7fa..869cc4e4e20 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,7 +43,7 @@ enum SysCacheIdentifier
AUTHMEMROLEMEM,
AUTHNAME,
AUTHOID,
- AUTHPASSWORD,
+ AUTHPASSWORDNAME,
CASTSOURCETARGET,
CLAAMNAMENSP,
CLAOID,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 4ffc41a5455..c6a84f86a84 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -120,7 +120,7 @@ CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
-- should not contain the original salt.
SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
- LEFT JOIN pg_auth_password p
+ LEFT JOIN pg_auth_password p
ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
--
2.31.1
[application/octet-stream] v1-0001-Move-rolpassword-out-of-pg_authid-into-a-new-table.patch (114.4K, ../../CAGB+Vh65WkmR1V5qVUerxmsJE9uBFqwd0oWTTNU=dca_ep2N5Q@mail.gmail.com/4-v1-0001-Move-rolpassword-out-of-pg_authid-into-a-new-table.patch)
download | inline diff:
From 9a4d6a7bb5c8e18e60ea1f7e747f5e024d2e6573 Mon Sep 17 00:00:00 2001
From: Joshua Brindle <[email protected]>
Date: Thu, 9 Dec 2021 16:18:25 -0800
Subject: [PATCH 1/3] Move rolpassword out of pg_authid into a new table
In preparation for more flexibility in password
management the rolpassword column needs to be
moved into a new table.
Signed-off-by: Joshua Brindle <[email protected]>
---
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/catalog.c | 7 +-
src/backend/catalog/system_views.sql | 5 +-
src/backend/commands/user.c | 226 ++++++++++++----
src/backend/libpq/auth-sasl.c | 2 +-
src/backend/libpq/auth-scram.c | 4 +-
src/backend/libpq/auth.c | 16 ++
src/backend/libpq/crypt.c | 40 ++-
src/backend/utils/cache/catcache.c | 1 +
src/backend/utils/cache/relcache.c | 13 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/initdb/initdb.c | 2 +-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/common/scram-common.c | 2 +-
src/include/catalog/pg_auth_password.h | 50 ++++
src/include/catalog/pg_authid.dat | 26 +-
src/include/catalog/pg_authid.h | 9 +-
src/include/commands/user.h | 1 +
src/include/libpq/crypt.h | 2 +-
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_index.out | 14 +-
src/test/regress/expected/oidjoins.out | 1 +
src/test/regress/expected/password.out | 44 ++--
src/test/regress/expected/roleattributes.out | 256 +++++++++----------
src/test/regress/expected/rules.out | 5 +-
src/test/regress/expected/tablespace.out | 12 +-
src/test/regress/sql/create_index.sql | 10 +-
src/test/regress/sql/password.sql | 32 ++-
src/test/regress/sql/roleattributes.sql | 64 ++---
src/test/regress/sql/tablespace.sql | 8 +-
30 files changed, 562 insertions(+), 318 deletions(-)
create mode 100644 src/include/catalog/pg_auth_password.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index eefebb7bb83..ea6d7e47340 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -61,7 +61,7 @@ CATALOG_HEADERS := \
pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
pg_database.h pg_db_role_setting.h pg_tablespace.h \
- pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
+ pg_authid.h pg_auth_members.h pg_auth_password.h pg_shdepend.h pg_shdescription.h \
pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index dfd5fb669ee..410b3328011 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -28,6 +28,7 @@
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
@@ -245,6 +246,7 @@ IsSharedRelation(Oid relationId)
{
/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
if (relationId == AuthIdRelationId ||
+ relationId == AuthPasswordRelationId ||
relationId == AuthMemRelationId ||
relationId == DatabaseRelationId ||
relationId == SharedDescriptionRelationId ||
@@ -258,6 +260,7 @@ IsSharedRelation(Oid relationId)
/* These are their indexes */
if (relationId == AuthIdRolnameIndexId ||
relationId == AuthIdOidIndexId ||
+ relationId == AuthPasswordRoleOidIndexId ||
relationId == AuthMemRoleMemIndexId ||
relationId == AuthMemMemRoleIndexId ||
relationId == DatabaseNameIndexId ||
@@ -275,8 +278,8 @@ IsSharedRelation(Oid relationId)
relationId == SubscriptionNameIndexId)
return true;
/* These are their toast tables and toast indexes */
- if (relationId == PgAuthidToastTable ||
- relationId == PgAuthidToastIndex ||
+ if (relationId == PgAuthPasswordToastTable ||
+ relationId == PgAuthPasswordToastIndex ||
relationId == PgDatabaseToastTable ||
relationId == PgDatabaseToastIndex ||
relationId == PgDbRoleSettingToastTable ||
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 40b7bca5a96..4dca955372a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -40,11 +40,14 @@ CREATE VIEW pg_shadow AS
rolsuper AS usesuper,
rolreplication AS userepl,
rolbypassrls AS usebypassrls,
- rolpassword AS passwd,
+ p.password AS passwd,
rolvaliduntil AS valuntil,
setconfig AS useconfig
FROM pg_authid LEFT JOIN pg_db_role_setting s
ON (pg_authid.oid = setrole AND setdatabase = 0)
+ LEFT JOIN pg_auth_password p
+ ON p.roleid = pg_authid.oid
+
WHERE rolcanlogin;
REVOKE ALL ON pg_shadow FROM public;
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index f9d3c1246bb..9ff99342eef 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -23,6 +23,7 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
#include "commands/comment.h"
@@ -64,6 +65,42 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Is the role able to log in
+*/
+bool
+is_role_valid(const char *rolename, char **logdetail)
+{
+ HeapTuple roleTup;
+ Datum datum;
+ bool isnull;
+ TimestampTz vuntil = 0;
+
+ /* Get role info from pg_authid */
+ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename));
+ if (!HeapTupleIsValid(roleTup))
+ {
+ *logdetail = psprintf(_("Role \"%s\" does not exist."),
+ rolename);
+ return false; /* no such user */
+ }
+
+ datum = SysCacheGetAttr(AUTHNAME, roleTup,
+ Anum_pg_authid_rolvaliduntil, &isnull);
+ ReleaseSysCache(roleTup);
+
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ if (!isnull && vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."), rolename);
+ return false;
+ }
+
+ return true;
+}
+
/*
* CREATE ROLE
@@ -351,43 +388,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
-
- if (password)
- {
- char *shadow_pass;
- const char *logdetail = NULL;
-
- /*
- * Don't allow an empty password. Libpq treats an empty password the
- * same as no password at all, and won't even try to authenticate. But
- * other clients might, so allowing it would be confusing. By clearing
- * the password when an empty string is specified, the account is
- * consistently locked for all clients.
- *
- * Note that this only covers passwords stored in the database itself.
- * There are also checks in the authentication code, to forbid an
- * empty password from being used with authentication methods that
- * fetch the password from an external system, like LDAP or PAM.
- */
- if (password[0] == '\0' ||
- plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
- {
- ereport(NOTICE,
- (errmsg("empty string is not a valid password, clearing password")));
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
- }
- else
- {
- /* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, stmt->role,
- password);
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(shadow_pass);
- }
- }
- else
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -422,6 +422,60 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
*/
CatalogTupleInsert(pg_authid_rel, tuple);
+ if (password)
+ {
+ char *shadow_pass;
+ char *logdetail;
+ Datum new_password_record[Natts_pg_auth_password];
+ bool new_password_record_nulls[Natts_pg_auth_password];
+ Relation pg_auth_password_rel;
+ TupleDesc pg_auth_password_dsc;
+ HeapTuple new_tuple;
+
+ /*
+ * Don't allow an empty password. Libpq treats an empty password the
+ * same as no password at all, and won't even try to authenticate. But
+ * other clients might, so allowing it would be confusing. By clearing
+ * the password when an empty string is specified, the account is
+ * consistently locked for all clients.
+ *
+ * Note that this only covers passwords stored in the database itself.
+ * There are also checks in the authentication code, to forbid an
+ * empty password from being used with authentication methods that
+ * fetch the password from an external system, like LDAP or PAM.
+ */
+ if (password[0] == '\0' ||
+ plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
+ {
+ ereport(NOTICE,
+ (errmsg("empty string is not a valid password, clearing password")));
+ }
+ else
+ {
+ /* Encrypt the password to the requested format. */
+ shadow_pass = encrypt_password(Password_encryption, stmt->role,
+ password);
+
+ MemSet(new_password_record, 0, sizeof(new_password_record));
+ MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
+
+ /* open password table and insert it. */
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
+
+ new_password_record[Anum_pg_auth_password_password - 1] =
+ CStringGetTextDatum(shadow_pass);
+ new_password_record[Anum_pg_auth_password_roleid - 1] = roleid;
+
+ new_tuple = heap_form_tuple(pg_auth_password_dsc,
+ new_password_record, new_password_record_nulls);
+ CatalogTupleInsert(pg_auth_password_rel, new_tuple);
+ heap_freetuple(new_tuple);
+ table_close(pg_auth_password_rel, NoLock);
+
+ }
+ }
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -495,6 +549,9 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
Datum new_record[Natts_pg_authid];
bool new_record_nulls[Natts_pg_authid];
bool new_record_repl[Natts_pg_authid];
+ Datum new_password_record[Natts_pg_auth_password];
+ bool new_password_record_nulls[Natts_pg_auth_password];
+ bool new_password_record_repl[Natts_pg_auth_password];
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
HeapTuple tuple,
@@ -624,6 +681,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
rolename = pstrdup(NameStr(authform->rolname));
roleid = authform->oid;
+
/*
* To mess with a superuser or replication role in any way you gotta be
* superuser. We also insist on superuser to change the BYPASSRLS
@@ -694,6 +752,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
+ MemSet(new_password_record, 0, sizeof(new_password_record));
+ MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
+ MemSet(new_password_record_repl, false, sizeof(new_password_record_repl));
+
/*
* issuper/createrole/etc
@@ -752,24 +814,24 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
ereport(NOTICE,
(errmsg("empty string is not a valid password, clearing password")));
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
}
else
{
/* Encrypt the password to the requested format. */
shadow_pass = encrypt_password(Password_encryption, rolename,
password);
- new_record[Anum_pg_authid_rolpassword - 1] =
+ new_password_record[Anum_pg_auth_password_password - 1] =
CStringGetTextDatum(shadow_pass);
}
- new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
}
/* unset password */
if (dpassword && dpassword->arg == NULL)
{
- new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
}
/* valid until */
@@ -786,12 +848,48 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
new_record_nulls, new_record_repl);
CatalogTupleUpdate(pg_authid_rel, &tuple->t_self, new_tuple);
-
- InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
-
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
+ if (new_password_record_repl[Anum_pg_auth_password_password - 1] == true)
+ {
+ Relation pg_auth_password_rel;
+ TupleDesc pg_auth_password_dsc;
+ HeapTuple password_tuple;
+
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
+ password_tuple = SearchSysCache1(AUTHPASSWORD, ObjectIdGetDatum(roleid));
+
+ if (new_password_record_nulls[Anum_pg_auth_password_password - 1] == true) /* delete existing password */
+ {
+ if (HeapTupleIsValid(password_tuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &password_tuple->t_self);
+ ReleaseSysCache(password_tuple);
+ }
+ }
+ else if (HeapTupleIsValid(password_tuple)) /* update existing password */
+ {
+ new_tuple = heap_modify_tuple(password_tuple, pg_auth_password_dsc, new_password_record,
+ new_password_record_nulls, new_password_record_repl);
+ CatalogTupleUpdate(pg_auth_password_rel, &password_tuple->t_self, new_tuple);
+ ReleaseSysCache(password_tuple);
+ heap_freetuple(new_tuple);
+ }
+ else /* insert new password */
+ {
+ new_password_record[Anum_pg_auth_password_roleid - 1] = roleid;
+
+ new_tuple = heap_form_tuple(pg_auth_password_dsc,
+ new_password_record, new_password_record_nulls);
+ CatalogTupleInsert(pg_auth_password_rel, new_tuple);
+ heap_freetuple(new_tuple);
+ }
+
+ table_close(pg_auth_password_rel, NoLock);
+ }
+ InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -901,7 +999,6 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
return roleid;
}
-
/*
* DROP ROLE
*/
@@ -909,7 +1006,8 @@ void
DropRole(DropRoleStmt *stmt)
{
Relation pg_authid_rel,
- pg_auth_members_rel;
+ pg_auth_members_rel,
+ pg_auth_password_rel;
ListCell *item;
if (!have_createrole_privilege())
@@ -923,6 +1021,8 @@ DropRole(DropRoleStmt *stmt)
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_auth_members_rel = table_open(AuthMemRelationId, RowExclusiveLock);
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+
foreach(item, stmt->roles)
{
@@ -1056,6 +1156,15 @@ DropRole(DropRoleStmt *stmt)
DeleteSharedComments(roleid, AuthIdRelationId);
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
+ /*
+ * Drop password
+ */
+ tuple = SearchSysCache1(AUTHPASSWORD, roleid);
+ if (HeapTupleIsValid(tuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &tuple->t_self);
+ ReleaseSysCache(tuple);
+ }
+
/*
* Remove settings for this role.
*/
@@ -1077,7 +1186,9 @@ DropRole(DropRoleStmt *stmt)
* Now we can clean up; but keep locks until commit.
*/
table_close(pg_auth_members_rel, NoLock);
+ table_close(pg_auth_password_rel, NoLock);
table_close(pg_authid_rel, NoLock);
+
}
/*
@@ -1087,11 +1198,12 @@ ObjectAddress
RenameRole(const char *oldname, const char *newname)
{
HeapTuple oldtuple,
- newtuple;
+ newtuple,
+ passtuple;
TupleDesc dsc;
Relation rel;
Datum datum;
- bool isnull;
+ bool isnull = true;
Datum repl_val[Natts_pg_authid];
bool repl_null[Natts_pg_authid];
bool repl_repl[Natts_pg_authid];
@@ -1189,14 +1301,24 @@ RenameRole(const char *oldname, const char *newname)
CStringGetDatum(newname));
repl_null[Anum_pg_authid_rolname - 1] = false;
- datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
+ passtuple = SearchSysCache1(AUTHPASSWORD, roleid);
+
+ if (HeapTupleIsValid(passtuple))
+ datum = SysCacheGetAttr(AUTHPASSWORD, passtuple,
+ Anum_pg_auth_password_password, &isnull);
if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
{
+ Relation pg_auth_password_rel;
+
/* MD5 uses the username as salt, so just clear it on a rename */
- repl_repl[Anum_pg_authid_rolpassword - 1] = true;
- repl_null[Anum_pg_authid_rolpassword - 1] = true;
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ if (HeapTupleIsValid(passtuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
+ ReleaseSysCache(passtuple);
+ }
+ table_close(pg_auth_password_rel, NoLock);
ereport(NOTICE,
(errmsg("MD5 password cleared because of role rename")));
}
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index a1d7dbb6d58..805b3695b78 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -33,7 +33,7 @@
* implementation.
*
* shadow_pass is an optional pointer to the stored secret of the role
- * authenticated, from pg_authid.rolpassword. For mechanisms that use
+ * authenticated, from pg_auth_password.password. For mechanisms that use
* shadowed passwords, a NULL pointer here means that an entry could not
* be found for the role (or the user does not exist), and the mechanism
* should fail the authentication exchange.
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index ee7f52218ab..795f1cba555 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -220,7 +220,7 @@ scram_get_mechanisms(Port *port, StringInfo buf)
* It should be one of the mechanisms that we support, as returned by
* scram_get_mechanisms().
*
- * 'shadow_pass' is the role's stored secret, from pg_authid.rolpassword.
+ * 'shadow_pass' is the role's stored secret, from pg_auth_password.password.
* The username was provided by the client in the startup message, and is
* available in port->user_name. If 'shadow_pass' is NULL, we still perform
* an authentication exchange, but it will fail, as if an incorrect password
@@ -454,7 +454,7 @@ scram_exchange(void *opaq, const char *input, int inputlen,
}
/*
- * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
+ * Construct a SCRAM secret, for storing in pg_auth_password.password.
*
* The result is palloc'd, so caller is responsible for freeing it.
*/
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index efc53f31353..5cfde1eaa13 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -592,10 +592,26 @@ ClientAuthentication(Port *port)
case uaMD5:
case uaSCRAM:
+ /*
+ * check to be sure we are not past rolvaliduntil
+ */
+ if (!is_role_valid(port->user_name, &logdetail)) {
+ status = STATUS_ERROR;
+ break;
+ }
+
status = CheckPWChallengeAuth(port, &logdetail);
break;
case uaPassword:
+ /*
+ * check to be sure we are not past rolvaliduntil
+ */
+ if (!is_role_valid(port->user_name, &logdetail)) {
+ status = STATUS_ERROR;
+ break;
+ }
+
status = CheckPasswordAuth(port, &logdetail);
break;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 1ff8b0507d4..745e61034c2 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -2,7 +2,7 @@
*
* crypt.c
* Functions for dealing with encrypted passwords stored in
- * pg_authid.rolpassword.
+ * pg_auth_password.password.
*
* Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -16,6 +16,7 @@
#include <unistd.h>
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "common/md5.h"
#include "common/scram-common.h"
#include "libpq/crypt.h"
@@ -36,8 +37,7 @@
char *
get_role_password(const char *role, const char **logdetail)
{
- TimestampTz vuntil = 0;
- HeapTuple roleTup;
+ HeapTuple roleTup, passTup;
Datum datum;
bool isnull;
char *shadow_pass;
@@ -51,33 +51,29 @@ get_role_password(const char *role, const char **logdetail)
return NULL; /* no such user */
}
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolpassword, &isnull);
- if (isnull)
+ datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_oid, &isnull);
+
+ passTup = SearchSysCache1(AUTHPASSWORD, datum);
+
+ if (!HeapTupleIsValid(passTup))
{
- ReleaseSysCache(roleTup);
*logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
return NULL; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
-
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolvaliduntil, &isnull);
- if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ datum = SysCacheGetAttr(AUTHPASSWORD, passTup,
+ Anum_pg_auth_password_password, &isnull);
ReleaseSysCache(roleTup);
-
- /*
- * Password OK, but check to be sure we are not past rolvaliduntil
- */
- if (!isnull && vuntil < GetCurrentTimestamp())
+ if (isnull) /* this should not happen any more but just in case */
{
- *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ ReleaseSysCache(passTup);
+ *logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
- return NULL;
+ return NULL; /* user has no password */
}
+ shadow_pass = TextDatumGetCString(datum);
+ ReleaseSysCache(passTup);
return shadow_pass;
}
@@ -156,7 +152,7 @@ encrypt_password(PasswordType target_type, const char *role,
* Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password or password hash, as stored
- * in pg_authid.rolpassword.
+ * in pg_auth_password.password.
* 'client_pass' is the response given by the remote user to the MD5 challenge.
* 'md5_salt' is the salt used in the MD5 authentication challenge.
*
@@ -211,7 +207,7 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
* Check given password for given user, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password hash, as stored in
- * pg_authid.rolpassword.
+ * pg_auth_password.password.
* 'client_pass' is the password given by the remote user.
*
* In the error case, store a string at *logdetail that will be sent to the
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index ec073e1ed06..356e9cde6fb 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1104,6 +1104,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
+ case AUTHPASSWORD:
case AUTHMEMMEMROLE:
case DATABASEOID:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fccffce5729..71773cb992c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_attrdef.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
@@ -112,6 +113,7 @@ static const FormData_pg_attribute Desc_pg_proc[Natts_pg_proc] = {Schema_pg_proc
static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type};
static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
+static const FormData_pg_attribute Desc_pg_auth_password[Natts_pg_auth_password] = {Schema_pg_auth_password};
static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel};
@@ -3461,6 +3463,7 @@ RelationBuildLocalRelation(const char *relname,
{
case DatabaseRelationId:
case AuthIdRelationId:
+ case AuthPasswordRelationId:
case AuthMemRelationId:
case RelationRelationId:
case AttributeRelationId:
@@ -3881,7 +3884,7 @@ RelationCacheInitialize(void)
* RelationCacheInitializePhase2
*
* This is called to prepare for access to shared catalogs during startup.
- * We must at least set up nailed reldescs for pg_database, pg_authid,
+ * We must at least set up nailed reldescs for pg_database, pg_authid, pg_auth_password,
* pg_auth_members, and pg_shseclabel. Ideally we'd like to have reldescs
* for their indexes, too. We attempt to load this information from the
* shared relcache init file. If that's missing or broken, just make
@@ -3926,8 +3929,10 @@ RelationCacheInitializePhase2(void)
Natts_pg_shseclabel, Desc_pg_shseclabel);
formrdesc("pg_subscription", SubscriptionRelation_Rowtype_Id, true,
Natts_pg_subscription, Desc_pg_subscription);
+ formrdesc("pg_auth_password", AuthPasswordRelation_Rowtype_Id, true,
+ Natts_pg_auth_password, Desc_pg_auth_password);
-#define NUM_CRITICAL_SHARED_RELS 5 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS 6 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
@@ -4066,8 +4071,10 @@ RelationCacheInitializePhase3(void)
AuthMemRelationId);
load_critical_index(SharedSecLabelObjectIndexId,
SharedSecLabelRelationId);
+ load_critical_index(AuthPasswordRoleOidIndexId,
+ AuthPasswordRelationId);
-#define NUM_CRITICAL_SHARED_INDEXES 6 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 7 /* fix if you change list above */
criticalSharedRelcachesBuilt = true;
}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index f4e7819f1e2..49d49f13252 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -27,6 +27,7 @@
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
@@ -254,6 +255,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {AuthPasswordRelationId, /* AUTHPASSWORD */
+ AuthPasswordRoleOidIndexId,
+ 1,
+ {
+ Anum_pg_auth_password_roleid,
+ 0,
+ 0,
+ 0
+ },
+ 8
+ },
{
CastRelationId, /* CASTSOURCETARGET */
CastSourceTargetIndexId,
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 97f15971e2b..8958e07fd30 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1444,7 +1444,7 @@ setup_auth(FILE *cmdfd)
* The authid table shouldn't be readable except through views, to
* ensure passwords are not publicly visible.
*/
- "REVOKE ALL ON pg_authid FROM public;\n\n",
+ "REVOKE ALL ON pg_auth_password FROM public;\n\n",
NULL
};
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9c9f7c6d63c..eb259d2b518 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -753,7 +753,18 @@ dumpRoles(PGconn *conn)
int i;
/* note: rolconfig is dumped later */
- if (server_version >= 90600)
+ if (server_version >= 150000)
+ printfPQExpBuffer(buf,
+ "SELECT oid, rolname, rolsuper, rolinherit, "
+ "rolcreaterole, rolcreatedb, "
+ "rolcanlogin, rolconnlimit, p.password as rolpassword, "
+ "rolvaliduntil, rolreplication, rolbypassrls, "
+ "pg_catalog.shobj_description(oid, '%s') as rolcomment, "
+ "rolname = current_user AS is_current_user "
+ "FROM %s LEFT JOIN pg_auth_password p ON %s.oid = p.roleid "
+ "WHERE rolname !~ '^pg_' "
+ "ORDER BY 2", role_catalog, role_catalog, role_catalog);
+ else if (server_version >= 90600)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 12686259299..d9d0cba7379 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -181,7 +181,7 @@ scram_ServerKey(const uint8 *salted_password, uint8 *result,
/*
- * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
+ * Construct a SCRAM secret, for storing in pg_auth_password.password.
*
* The password should already have been processed with SASLprep, if necessary!
*
diff --git a/src/include/catalog/pg_auth_password.h b/src/include/catalog/pg_auth_password.h
new file mode 100644
index 00000000000..beaa2d40b90
--- /dev/null
+++ b/src/include/catalog/pg_auth_password.h
@@ -0,0 +1,50 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_password.h
+ * definition of the "authorization identifier" system catalog (pg_auth_password)
+ *
+ * Portions Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/pg_auth_password.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_PASSWORD_H
+#define PG_AUTH_PASSWORD_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_auth_password_d.h"
+
+/* ----------------
+ * pg_auth_password definition. cpp turns this into
+ * typedef struct FormData_pg_auth_password
+ * ----------------
+ */
+CATALOG(pg_auth_password,4548,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4549,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
+{
+ Oid roleid BKI_LOOKUP(pg_authid); /* ID of a role */
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text password; /* password */
+#endif
+} FormData_pg_auth_password;
+
+/* ----------------
+ * Form_pg_auth_password corresponds to a pointer to a tuple with
+ * the format of pg_auth_password relation.
+ * ----------------
+ */
+
+DECLARE_TOAST(pg_auth_password, 4175, 4176);
+#define PgAuthPasswordToastTable 4175
+#define PgAuthPasswordToastIndex 4176
+
+typedef FormData_pg_auth_password *Form_pg_auth_password;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4550, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops));
+
+
+#endif /* PG_AUTH_PASSWORD_H */
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 6c28119fa1a..da36375a582 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -23,66 +23,66 @@
rolname => 'POSTGRES', rolsuper => 't', rolinherit => 't',
rolcreaterole => 't', rolcreatedb => 't', rolcanlogin => 't',
rolreplication => 't', rolbypassrls => 't', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6171', oid_symbol => 'ROLE_PG_DATABASE_OWNER',
rolname => 'pg_database_owner', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6181', oid_symbol => 'ROLE_PG_READ_ALL_DATA',
rolname => 'pg_read_all_data', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6182', oid_symbol => 'ROLE_PG_WRITE_ALL_DATA',
rolname => 'pg_write_all_data', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3373', oid_symbol => 'ROLE_PG_MONITOR',
rolname => 'pg_monitor', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3374', oid_symbol => 'ROLE_PG_READ_ALL_SETTINGS',
rolname => 'pg_read_all_settings', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3375', oid_symbol => 'ROLE_PG_READ_ALL_STATS',
rolname => 'pg_read_all_stats', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3377', oid_symbol => 'ROLE_PG_STAT_SCAN_TABLES',
rolname => 'pg_stat_scan_tables', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4569', oid_symbol => 'ROLE_PG_READ_SERVER_FILES',
rolname => 'pg_read_server_files', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4570', oid_symbol => 'ROLE_PG_WRITE_SERVER_FILES',
rolname => 'pg_write_server_files', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4571', oid_symbol => 'ROLE_PG_EXECUTE_SERVER_PROGRAM',
rolname => 'pg_execute_server_program', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4200', oid_symbol => 'ROLE_PG_SIGNAL_BACKEND',
rolname => 'pg_signal_backend', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4544', oid_symbol => 'ROLE_PG_CHECKPOINTER',
rolname => 'pg_checkpointer', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
]
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index 4b65e39a1f9..0cb5675a33f 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -42,9 +42,8 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
int32 rolconnlimit; /* max connections allowed (-1=no limit) */
/* remaining fields may be null; use heap_getattr to read them! */
-#ifdef CATALOG_VARLEN /* variable-length fields start here */
- text rolpassword; /* password, if any */
- timestamptz rolvaliduntil; /* password expiration time, if any */
+#ifdef CATALOG_VARLEN
+ timestamptz rolvaliduntil BKI_FORCE_NULL; /* role expiration time, if any */
#endif
} FormData_pg_authid;
@@ -55,10 +54,6 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
*/
typedef FormData_pg_authid *Form_pg_authid;
-DECLARE_TOAST(pg_authid, 4175, 4176);
-#define PgAuthidToastTable 4175
-#define PgAuthidToastIndex 4176
-
DECLARE_UNIQUE_INDEX(pg_authid_rolname_index, 2676, AuthIdRolnameIndexId, on pg_authid using btree(rolname name_ops));
DECLARE_UNIQUE_INDEX_PKEY(pg_authid_oid_index, 2677, AuthIdOidIndexId, on pg_authid using btree(oid oid_ops));
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 0b7a3cd65fd..ce39db5a491 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -24,6 +24,7 @@ typedef void (*check_password_hook_type) (const char *username, const char *shad
extern PGDLLIMPORT check_password_hook_type check_password_hook;
+extern bool is_role_valid(const char *rolename, char **logdetail);
extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 3238cf66d3a..b8ff8ccb417 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -21,7 +21,7 @@
* Plaintext passwords can be passed in by the user, in a CREATE/ALTER USER
* command. They will be encrypted to MD5 or SCRAM-SHA-256 format, before
* storing on-disk, so only MD5 and SCRAM-SHA-256 passwords should appear
- * in pg_authid.rolpassword. They are also the allowed values for the
+ * in pg_auth_password.password. They are also the allowed values for the
* password_encryption GUC.
*/
typedef enum PasswordType
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 9c1a76e8bb6..ab846e8b7fa 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,6 +43,7 @@ enum SysCacheIdentifier
AUTHMEMROLEMEM,
AUTHNAME,
AUTHOID,
+ AUTHPASSWORD,
CASTSOURCETARGET,
CLAAMNAMENSP,
CLAOID,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index d55aec3a1d0..9875b277f26 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2515,10 +2515,10 @@ REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
ERROR: cannot reindex system catalogs concurrently
REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
ERROR: cannot reindex system catalogs concurrently
--- These are the toast table and index of pg_authid.
-REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
+-- These are the toast table and index of pg_database.
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1262; -- no catalog toast table
ERROR: cannot reindex system catalogs concurrently
-REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -- no catalog toast index
ERROR: cannot reindex system catalogs concurrently
REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
ERROR: cannot reindex system catalogs concurrently
@@ -2817,10 +2817,10 @@ ERROR: must be owner of schema schema_to_reindex
RESET ROLE;
GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
SET SESSION ROLE regress_reindexuser;
-REINDEX TABLE pg_toast.pg_toast_1260;
-ERROR: must be owner of table pg_toast_1260
-REINDEX INDEX pg_toast.pg_toast_1260_index;
-ERROR: must be owner of index pg_toast_1260_index
+REINDEX TABLE pg_toast.pg_toast_1262;
+ERROR: must be owner of table pg_toast_1262
+REINDEX INDEX pg_toast.pg_toast_1262_index;
+ERROR: must be owner of index pg_toast_1262_index
-- Clean up
RESET ROLE;
REVOKE USAGE ON SCHEMA pg_toast FROM regress_reindexuser;
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be3..b69a2a72a7b 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -197,6 +197,7 @@ NOTICE: checking pg_tablespace {spcowner} => pg_authid {oid}
NOTICE: checking pg_auth_members {roleid} => pg_authid {oid}
NOTICE: checking pg_auth_members {member} => pg_authid {oid}
NOTICE: checking pg_auth_members {grantor} => pg_authid {oid}
+NOTICE: checking pg_auth_password {roleid} => pg_authid {oid}
NOTICE: checking pg_shdepend {dbid} => pg_database {oid}
NOTICE: checking pg_shdepend {classid} => pg_class {oid}
NOTICE: checking pg_shdepend {refclassid} => pg_class {oid}
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 7c84c9da337..4ffc41a5455 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -24,10 +24,12 @@ CREATE ROLE regress_passwd4 PASSWORD NULL;
--
-- Since the salt is random, the exact value stored will be different on every test
-- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
rolname | rolpassword_masked
-----------------+---------------------------------------------------
regress_passwd1 | md5783277baca28003b33453252be4dbb34
@@ -40,12 +42,14 @@ SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+
ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new;
NOTICE: MD5 password cleared because of role rename
-- md5 entry should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd2_new'
- ORDER BY rolname, rolpassword;
- rolname | rolpassword
----------------------+-------------
+ ORDER BY rolname, password;
+ rolname | password
+---------------------+----------
regress_passwd2_new |
(1 row)
@@ -72,10 +76,12 @@ CREATE ROLE regress_passwd6 PASSWORD 'SCRAM-SHA-256$1234';
CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
-- invalid length
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
rolname | rolpassword_masked
-----------------+---------------------------------------------------
regress_passwd1 | md5cd3578025fe2c3d7ed1b9a9b26238b70
@@ -95,9 +101,11 @@ ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
NOTICE: empty string is not a valid password, clearing password
ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
NOTICE: empty string is not a valid password, clearing password
-SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
- rolpassword
--------------
+SELECT password FROM pg_authid
+LEFT JOIN pg_auth_password p
+ON pg_authid.oid = p.roleid WHERE rolname='regress_passwd_empty';
+ password
+----------
(1 row)
@@ -110,8 +118,10 @@ CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
-- Check that the invalid secrets were re-hashed. A re-hashed secret
-- should not contain the original salt.
-SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
+SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
rolname | is_rolpassword_rehashed
@@ -134,11 +144,13 @@ DROP ROLE regress_passwd_sha_len0;
DROP ROLE regress_passwd_sha_len1;
DROP ROLE regress_passwd_sha_len2;
-- all entries should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
- rolname | rolpassword
----------+-------------
+ ORDER BY rolname, password;
+ rolname | password
+---------+----------
(0 rows)
diff --git a/src/test/regress/expected/roleattributes.out b/src/test/regress/expected/roleattributes.out
index 5e6969b173e..18a9dacadde 100644
--- a/src/test/regress/expected/roleattributes.out
+++ b/src/test/regress/expected/roleattributes.out
@@ -1,233 +1,233 @@
-- default for superuser is false
CREATE ROLE regress_test_def_superuser;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_superuser | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
-- default for inherit is true
CREATE ROLE regress_test_def_inherit;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_inherit | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_inherit WITH INHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
-- default for create role is false
CREATE ROLE regress_test_def_createrole;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_createrole | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
-- default for create database is false
CREATE ROLE regress_test_def_createdb;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_createdb | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createdb WITH NOCREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
-- default for can login is false for role
CREATE ROLE regress_test_def_role_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_role_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
-- default for can login is true for user
CREATE USER regress_test_def_user_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_user_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
CREATE USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER USER regress_test_user_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
-- default for replication is false
CREATE ROLE regress_test_def_replication;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_replication | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_replication | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | t | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
ALTER ROLE regress_test_replication WITH NOREPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | t | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
-- default for bypassrls is false
CREATE ROLE regress_test_def_bypassrls;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_bypassrls | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
-- clean up roles
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac468568a1a..353917c9f81 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1710,11 +1710,12 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
pg_authid.rolsuper AS usesuper,
pg_authid.rolreplication AS userepl,
pg_authid.rolbypassrls AS usebypassrls,
- pg_authid.rolpassword AS passwd,
+ p.password AS passwd,
pg_authid.rolvaliduntil AS valuntil,
s.setconfig AS useconfig
- FROM (pg_authid
+ FROM ((pg_authid
LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
+ LEFT JOIN pg_auth_password p ON ((p.roleid = pg_authid.oid)))
WHERE pg_authid.rolcanlogin;
pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
pg_get_shmem_allocations.off,
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 2dfbcfdebe1..f013c931087 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -44,13 +44,13 @@ ERROR: cannot move system relation "pg_authid_rolname_index"
REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
ERROR: cannot reindex system catalogs concurrently
-- toast relations, fail
-REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
-ERROR: cannot move system relation "pg_toast_1260_index"
-REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1260_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index;
+ERROR: cannot move system relation "pg_toast_1262_index"
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1262_index;
ERROR: cannot reindex system catalogs concurrently
-REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
-ERROR: cannot move system relation "pg_toast_1260_index"
-REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262;
+ERROR: cannot move system relation "pg_toast_1262_index"
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1262;
ERROR: cannot reindex system catalogs concurrently
-- system catalog, fail
REINDEX (TABLESPACE pg_global) TABLE pg_authid;
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index d8fded3d930..8d649f2355f 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -1068,9 +1068,9 @@ REINDEX TABLE CONCURRENTLY concur_reindex_tab;
COMMIT;
REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
--- These are the toast table and index of pg_authid.
-REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
-REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index
+-- These are the toast table and index of pg_database.
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1262; -- no catalog toast table
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -- no catalog toast index
REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
-- Warns about catalog relations
REINDEX SCHEMA CONCURRENTLY pg_catalog;
@@ -1243,8 +1243,8 @@ REINDEX SCHEMA schema_to_reindex;
RESET ROLE;
GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
SET SESSION ROLE regress_reindexuser;
-REINDEX TABLE pg_toast.pg_toast_1260;
-REINDEX INDEX pg_toast.pg_toast_1260_index;
+REINDEX TABLE pg_toast.pg_toast_1262;
+REINDEX INDEX pg_toast.pg_toast_1262_index;
-- Clean up
RESET ROLE;
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 98f49916e5d..bb0c815121f 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -23,18 +23,22 @@ CREATE ROLE regress_passwd4 PASSWORD NULL;
--
-- Since the salt is random, the exact value stored will be different on every test
-- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
-- Rename a role
ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new;
-- md5 entry should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd2_new'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
ALTER ROLE regress_passwd2_new RENAME TO regress_passwd2;
-- Change passwords with ALTER USER. With plaintext or already-encrypted
@@ -63,16 +67,20 @@ CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
-- invalid length
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
-- An empty password is not allowed, in any form
CREATE ROLE regress_passwd_empty PASSWORD '';
ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
-SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
+SELECT password FROM pg_authid
+LEFT JOIN pg_auth_password p
+ON pg_authid.oid = p.roleid WHERE rolname='regress_passwd_empty';
-- Test with invalid stored and server keys.
--
@@ -84,8 +92,10 @@ CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
-- Check that the invalid secrets were re-hashed. A re-hashed secret
-- should not contain the original salt.
-SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
+SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
@@ -103,7 +113,9 @@ DROP ROLE regress_passwd_sha_len1;
DROP ROLE regress_passwd_sha_len2;
-- all entries should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
diff --git a/src/test/regress/sql/roleattributes.sql b/src/test/regress/sql/roleattributes.sql
index c961b2d7303..09505c6a3be 100644
--- a/src/test/regress/sql/roleattributes.sql
+++ b/src/test/regress/sql/roleattributes.sql
@@ -1,83 +1,83 @@
-- default for superuser is false
CREATE ROLE regress_test_def_superuser;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
CREATE ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
ALTER ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
-- default for inherit is true
CREATE ROLE regress_test_def_inherit;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
CREATE ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
ALTER ROLE regress_test_inherit WITH INHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
ALTER ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
-- default for create role is false
CREATE ROLE regress_test_def_createrole;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
CREATE ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
ALTER ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
-- default for create database is false
CREATE ROLE regress_test_def_createdb;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
CREATE ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
ALTER ROLE regress_test_createdb WITH NOCREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
ALTER ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
-- default for can login is false for role
CREATE ROLE regress_test_def_role_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
CREATE ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
ALTER ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
-- default for can login is true for user
CREATE USER regress_test_def_user_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
CREATE USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
ALTER USER regress_test_user_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
ALTER USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
-- default for replication is false
CREATE ROLE regress_test_def_replication;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
CREATE ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
ALTER ROLE regress_test_replication WITH NOREPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
ALTER ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
-- default for bypassrls is false
CREATE ROLE regress_test_def_bypassrls;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
-- clean up roles
DROP ROLE regress_test_def_superuser;
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 896f05cea32..24208c20c3d 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -38,10 +38,10 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am;
REINDEX (TABLESPACE regress_tblspace) TABLE pg_authid;
REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
-- toast relations, fail
-REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
-REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1260_index;
-REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
-REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1262_index;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262;
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1262;
-- system catalog, fail
REINDEX (TABLESPACE pg_global) TABLE pg_authid;
REINDEX (TABLESPACE pg_global) TABLE CONCURRENTLY pg_authid;
--
2.31.1
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2022-04-08 17:04 Joshua Brindle <[email protected]>
parent: Joshua Brindle <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Joshua Brindle @ 2022-04-08 17:04 UTC (permalink / raw)
To: pgsql-hackers; +Cc: Stephen Frost <[email protected]>
> > On Wed, Mar 2, 2022 at 9:58 AM Joshua Brindle
> > <[email protected]> wrote:
> > >
> > > This is not intended for PG15.
> > >
> > > Attached are a proof of concept patchset to implement multiple valid
> > > passwords, which have independent expirations, set by a GUC or SQL
> > > using an interval.
> > >
> >
> > <snip>
> >
> > > postgres=# select * from pg_auth_password ;
> > > roleid | name |
> > > password
> > > | expiration
> > > --------+---------+-------------------------------------------------------------------------------------------------------------------
> > > --------------------+-------------------------------
> > > 10 | __def__ |
> > > SCRAM-SHA-256$4096:yGiHIYPwc2az7xj/7TIyTA==$OQL/AEcEY1yOCNbrZEj4zDvNnOLpIqltOW1uQvosLvc=:9VRRppuIkSrwhiBN5ePy8wB1y
> > > zDa/2uX0WUx6gXi93E= |
> > > 16384 | __def__ |
> > > SCRAM-SHA-256$4096:AAAAAAAAAAAAAAAAAAAAAA==$1Ivp4d+vAWxowpuGEn05KR9lxyGOms3yy85k3D7XpBg=:k8xUjU6xrJG17PMGa/Zya6pAE
> > > /M7pEDaoIFmWvNIEUg= | 2022-03-02 06:52:31.217193-08
> > > 16384 | newone |
> > > SCRAM-SHA-256$4096:AAAAAAAAAAAAAAAAAAAAAA==$WK3+41CCGDognSnZrtpHhv00z9LuVUjHR1hWq8T1+iE=:w2C5GuhgiEB7wXqPxYfxBKB+e
> > > hm4h6Oeif1uzpPIFVk= | 2022-03-03 06:47:53.728159-08
> > > (3 rows)
> >
> > There's obviously a salt problem here that I'll need to fix that
> > apparently snuck in at the last rebase, but this brings up one aspect
> > of the patchset I didn't mention in the original email:
> >
>
> Attached are fixed patches rebased against the lastest master.
>
>
> > For the SCRAM protocol to work as is with existing clients the salt
> > for each password must be the same. Right now ALTER USER will find and
> > reuse the salt, but a user passing in a pre-computed SCRAM secret
> > currently has no way to get the salt.
> >
> > for \password (we'll need a new one that takes a password name) I was
> > thinking libpq could hold onto the salt that was used to log in, but
> > for outside computation we'll need some way for the client to request
> > it.
> >
> > None of that is done yet.
Now that the commitfest is over these are rebased on master.
It's unclear if I will be able to continue working on this featureset,
this email address will be inactive after today.
Attachments:
[application/x-patch] v2-0003-Per-password-expiration.patch (19.3K, ../../CAGB+Vh5Ed65gCmjJxCYguVoBoiDLePYDAeHMkb6-7PR-DFU-yQ@mail.gmail.com/2-v2-0003-Per-password-expiration.patch)
download | inline diff:
From 6a52b2c16e99e2d9c734525cc0b75a3561e0e2c7 Mon Sep 17 00:00:00 2001
From: Joshua Brindle <[email protected]>
Date: Wed, 2 Mar 2022 06:07:40 -0800
Subject: [PATCH v2 3/3] Per-password expiration
To build on the multi-password support this
adds per-password expiration, either passed in via ALTER ROLE/CREATE
ROLE with the grammar EXPIRES IN, or via a system-wide setting
called password_valid_duration
Signed-off-by: Joshua Brindle <[email protected]>
---
src/backend/commands/user.c | 121 ++++++++++++++++++++++++-
src/backend/commands/variable.c | 105 +++++++++++++++++++++
src/backend/libpq/auth.c | 9 +-
src/backend/libpq/crypt.c | 38 +++++++-
src/backend/parser/gram.y | 11 ++-
src/backend/utils/misc/guc.c | 13 +++
src/include/catalog/pg_auth_password.h | 1 +
src/include/commands/user.h | 3 +
src/include/commands/variable.h | 4 +
src/include/parser/kwlist.h | 1 +
10 files changed, 292 insertions(+), 14 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 4117e58840a..e13f984ae09 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -48,8 +48,9 @@
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
-/* GUC parameter */
+/* GUC parameters */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
+Interval *default_password_duration = NULL;
/* default password name */
const char* default_passname = "__def__";
@@ -167,6 +168,84 @@ validate_and_get_salt(char *rolename, char **salt, const char **logdetail)
return true;
}
+static
+Datum
+expires_in_datum(DefElem *passExpiresIn)
+{
+ Interval *interval;
+ Node *arg;
+ A_Const *con;
+ TimestampTz now = GetCurrentTimestamp();
+ Datum passExpiresIn_datum;
+ char *dateout;
+
+ if (default_password_duration != NULL)
+ {
+ /* The default duration GUC is set, use it if nothing came from SQL
+ * or if something came from SQL, reject it if not from a superuser
+ */
+
+ if (passExpiresIn != NULL)
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to override password_validity_duration GUC")));
+ else
+ goto bypass;
+ else
+ {
+ passExpiresIn_datum = DirectFunctionCall2(timestamptz_pl_interval,
+ TimestampGetDatum(now),
+ PointerGetDatum(default_password_duration));
+
+ dateout =
+ DatumGetCString(DirectFunctionCall1(timestamp_out,
+ passExpiresIn_datum));
+
+ ereport(NOTICE,
+ (errmsg("Password will expire at: \"%s\" (from GUC)", dateout)));
+
+ return passExpiresIn_datum;
+ }
+ }
+
+ if (passExpiresIn == NULL) /* No duration requested via SQL and no system default, no expiration */
+ return PointerGetDatum(NULL);
+
+bypass:
+ arg = (Node *)passExpiresIn->arg;
+ if (IsA(arg, TypeCast))
+ {
+ TypeCast *tc = (TypeCast *) passExpiresIn->arg;
+ arg = tc->arg;
+ }
+
+ if (!IsA(arg, A_Const))
+ {
+ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg));
+ return PointerGetDatum(NULL);
+ }
+ con = (A_Const *) arg;
+
+ interval = DatumGetIntervalP(DirectFunctionCall3(interval_in,
+ CStringGetDatum(strVal(&con->val)),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1)));
+
+ passExpiresIn_datum = DirectFunctionCall2(timestamptz_pl_interval,
+ TimestampGetDatum(now),
+ PointerGetDatum(interval));
+
+ dateout =
+ DatumGetCString(DirectFunctionCall1(timestamp_out,
+ passExpiresIn_datum));
+
+ ereport(NOTICE,
+ (errmsg("Password will expire at: \"%s\" (from SQL)", dateout)));
+
+ return passExpiresIn_datum;
+}
+
/*
* CREATE ROLE
*/
@@ -211,7 +290,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
DefElem *dpassName = NULL;
-
+ DefElem *dpassExpiresIn = NULL;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -320,6 +399,12 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dpassName = defel;
}
+ else if (strcmp(defel->defname, "expiresin") == 0)
+ {
+ if (dpassExpiresIn)
+ errorConflictingDefElem(defel, pstate);
+ dpassExpiresIn = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
@@ -508,6 +593,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
Relation pg_auth_password_rel;
TupleDesc pg_auth_password_dsc;
HeapTuple new_tuple;
+ Datum passExpiresIn_datum;
/*
* Don't allow an empty password. Libpq treats an empty password the
@@ -529,19 +615,27 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
}
else
{
+ MemSet(new_password_record, 0, sizeof(new_password_record));
+ MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
+
+ passExpiresIn_datum = expires_in_datum(dpassExpiresIn);
+ if (passExpiresIn_datum != PointerGetDatum(NULL))
+ new_password_record[Anum_pg_auth_password_expiration - 1] = passExpiresIn_datum;
+ else
+ new_password_record_nulls[Anum_pg_auth_password_expiration - 1] = true;
+
/* Encrypt the password to the requested format. */
validate_and_get_salt(stmt->role, &salt, &logdetail);
shadow_pass = encrypt_password(Password_encryption, salt,
password);
- MemSet(new_password_record, 0, sizeof(new_password_record));
- MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
if (passname != NULL)
new_password_record[Anum_pg_auth_password_name - 1] =
DirectFunctionCall1(namein, CStringGetDatum(passname));
else
new_password_record[Anum_pg_auth_password_name - 1] =
DirectFunctionCall1(namein, CStringGetDatum(default_passname));
+
/* open password table and insert it. */
pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
@@ -648,6 +742,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
+ Datum passExpiresIn_datum; /* Time period until password expires */
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
@@ -660,6 +755,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
DefElem *dpassName = NULL;
+ DefElem *dpassExpiresIn = NULL;
Oid roleid;
check_rolespec_name(stmt->role,
@@ -743,6 +839,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dpassName = defel;
}
+ else if (strcmp(defel->defname, "expiresin") == 0)
+ {
+ if (dpassExpiresIn)
+ errorConflictingDefElem(defel, pstate);
+ dpassExpiresIn = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -763,6 +865,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dpassName)
passname = strVal(dpassName->arg);
+
/*
* Scan the pg_authid relation to be certain the user exists.
*/
@@ -849,6 +952,13 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
MemSet(new_password_record_repl, false, sizeof(new_password_record_repl));
+ passExpiresIn_datum = expires_in_datum(dpassExpiresIn);
+ if (passExpiresIn_datum != PointerGetDatum(NULL)) {
+ new_password_record[Anum_pg_auth_password_expiration - 1] = passExpiresIn_datum;
+ new_password_record_repl[Anum_pg_auth_password_expiration - 1] = true;
+ }
+ else
+ new_password_record_nulls[Anum_pg_auth_password_expiration - 1] = true;
/*
* issuper/createrole/etc
@@ -958,7 +1068,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
- if (new_password_record_repl[Anum_pg_auth_password_password - 1] == true)
+ if (new_password_record_repl[Anum_pg_auth_password_password - 1] == true
+ || new_password_record_repl[Anum_pg_auth_password_expiration - 1] == true)
{
Relation pg_auth_password_rel;
TupleDesc pg_auth_password_dsc;
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index e5ddcda0b4a..ca85a10b80b 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -24,6 +24,7 @@
#include "access/xlog.h"
#include "catalog/pg_authid.h"
#include "commands/variable.h"
+#include "commands/user.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/acl.h"
@@ -933,3 +934,107 @@ show_role(void)
/* Otherwise we can just use the GUC string */
return role_string ? role_string : "none";
}
+
+
+/*
+ * check_password_duration: GUC check_hook for password_duration
+ */
+bool
+check_password_duration(char **newval, void **extra, GucSource source)
+{
+ Interval *new_interval;
+ char *endptr;
+
+ const char *valueptr = *newval;
+ char *val;
+ Interval *interval;
+
+ if (newval == NULL || *newval == NULL) {
+ extra = NULL;
+ return true;
+ }
+
+ elog(NOTICE,"Setting password duration to \"%s\"",
+ *newval);
+
+ while (isspace((unsigned char) *valueptr))
+ valueptr++;
+ if (*valueptr != '\'') {
+ val = pstrdup(valueptr);
+ }
+ else
+ {
+ valueptr++;
+ 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) {
+ return false;
+ }
+
+ new_interval = malloc(sizeof(Interval));
+ memcpy(new_interval, interval, sizeof(Interval));
+ pfree(interval);
+
+ /*
+ * Pass back data for assign_password_validity to use
+ */
+ *extra = malloc(sizeof(Interval *));
+ if (!*extra)
+ return false;
+ *((Interval **) *extra) = new_interval;
+
+ return true;
+}
+
+/*
+ * assign_password_validity: GUC assign_hook for timezone
+ */
+void
+assign_password_duration(const char *newval, void *extra)
+{
+ if (extra == NULL)
+ default_password_duration = NULL;
+ else
+ default_password_duration = *((Interval **) extra);
+}
+
+/*
+ * show_password_validity: GUC show_hook for timezone
+ */
+const char *
+show_password_duration(void)
+{
+ const char *intervalout;
+ if (default_password_duration == NULL) {
+ return "";
+ }
+ intervalout = DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(default_password_duration)));
+
+ if (intervalout != NULL)
+ return intervalout;
+
+ return "";
+}
\ No newline at end of file
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 5569599ab31..641cc92549b 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -855,9 +855,14 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
auth_result = CheckSASLAuth(&pg_be_scram_mech, port, (const char **) passwords, num,
logdetail);
- for (i = 0; i < num; i++)
- pfree(passwords[i]);
+ for (i = 0; i < num; i++) {
+ if (passwords[i] != NULL)
+ pfree(passwords[i]);
+ else
+ ereport(LOG,
+ (errmsg("Password %d was null", i)));
+ }
pfree(passwords);
}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 09b6bebf40c..8efb73b6c29 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -40,10 +40,12 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
{
HeapTuple roleTup;
Datum datum;
+ TimestampTz current, vuntil = 0;
+
bool isnull;
char **passwords;
CatCList *passlist;
- int i;
+ int i, j = 0, num_valid_passwords = 0;
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
@@ -51,6 +53,7 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
{
*logdetail = psprintf(_("Role \"%s\" does not exist."),
role);
+ *num = 0;
return NULL; /* no such user */
}
@@ -58,7 +61,6 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
ReleaseSysCache(roleTup);
/* Find any existing password that is not the one being updated to get the salt */
passlist = SearchSysCacheList1(AUTHPASSWORDNAME, datum);
- *num = passlist->n_members;
if (passlist->n_members == 0)
{
@@ -69,15 +71,41 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
return NULL; /* user has no password */
}
- passwords = palloc0(sizeof(char *) * passlist->n_members);
+ current = GetCurrentTimestamp();
for (i = 0; i < passlist->n_members; i++)
{
HeapTuple tup = &passlist->members[i]->tuple;
datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
- Anum_pg_auth_password_password, &isnull);
- passwords[i] = TextDatumGetCString(datum);
+ Anum_pg_auth_password_expiration, &isnull);
+
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ if (isnull || vuntil > current)
+ num_valid_passwords++;
+ }
+
+ passwords = palloc0(sizeof(char *) * num_valid_passwords);
+ *num = num_valid_passwords;
+
+ for (i = 0; i < passlist->n_members; i++)
+ {
+ HeapTuple tup = &passlist->members[i]->tuple;
+
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_expiration, &isnull);
+
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ if (isnull || vuntil > current)
+ {
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_password, &isnull);
+ passwords[j++] = pstrdup(TextDatumGetCString(datum));
+ }
}
ReleaseCatCacheList(passlist);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c3cb6c76aa5..0121cc79ee6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -790,7 +790,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DOUBLE_P DROP
EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE
- EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
+ EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPIRES EXPLAIN EXPRESSION
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
@@ -826,7 +826,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
-
PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSNAME PASSWORD
PATH PLACING PLAN PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
@@ -1262,6 +1261,12 @@ AlterOptRoleElem:
$$ = makeDefElem("passname",
(Node *)makeString($2), @1);
}
+ | EXPIRES IN_P Sconst opt_interval
+ {
+ TypeName *t = SystemTypeName("interval");
+ t->typmods = $4;
+ $$ = makeDefElem("expiresin", (Node *)makeStringConstCast($3, @3, t), @1);
+ }
| VALID FOR Sconst
{
$$ = makeDefElem("validFor", (Node *)makeString($3), @1);
@@ -16986,6 +16991,7 @@ unreserved_keyword:
| EXCLUDING
| EXCLUSIVE
| EXECUTE
+ | EXPIRES
| EXPLAIN
| EXPRESSION
| EXTENSION
@@ -17560,6 +17566,7 @@ bare_label_keyword:
| EXCLUSIVE
| EXECUTE
| EXISTS
+ | EXPIRES
| EXPLAIN
| EXPRESSION
| EXTENSION
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 22b5571a704..81208c00ea1 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -716,6 +716,7 @@ static char *recovery_target_string;
static char *recovery_target_xid_string;
static char *recovery_target_name_string;
static char *recovery_target_lsn_string;
+static char *password_duration_string;
/* should be static, but commands/variable.c needs to get at this */
@@ -4721,6 +4722,17 @@ static struct config_string ConfigureNamesString[] =
check_backtrace_functions, assign_backtrace_functions, NULL
},
+ {
+ {"password_valid_duration", PGC_SUSET, CONN_AUTH_AUTH,
+ gettext_noop("Specifies the default validity duration of new passwords."),
+ NULL,
+ GUC_SUPERUSER_ONLY | GUC_NOT_IN_SAMPLE
+ },
+ &password_duration_string,
+ NULL,
+ check_password_duration, assign_password_duration, show_password_duration
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
@@ -5119,6 +5131,7 @@ static struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
diff --git a/src/include/catalog/pg_auth_password.h b/src/include/catalog/pg_auth_password.h
index 4181caad30b..1cb0a211ee7 100644
--- a/src/include/catalog/pg_auth_password.h
+++ b/src/include/catalog/pg_auth_password.h
@@ -31,6 +31,7 @@ CATALOG(pg_auth_password,4551,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_RO
#ifdef CATALOG_VARLEN /* variable-length fields start here */
text password BKI_FORCE_NOT_NULL; /* password */
+ timestamptz expiration BKI_FORCE_NULL; /* password expiration time, if any */
#endif
} FormData_pg_auth_password;
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 1c14f60a05b..13d3479facb 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -19,6 +19,9 @@
/* GUC. Is actually of type PasswordType. */
extern PGDLLIMPORT int Password_encryption;
+/* GUC. system-wide password validity duration */
+extern Interval *default_password_duration;
+
/* Hook to check passwords in CreateRole() and AlterRole() */
typedef void (*check_password_hook_type) (const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null);
diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h
index 0e5ddcbcf37..3b941c554cb 100644
--- a/src/include/commands/variable.h
+++ b/src/include/commands/variable.h
@@ -34,5 +34,9 @@ extern void assign_session_authorization(const char *newval, void *extra);
extern bool check_role(char **newval, void **extra, GucSource source);
extern void assign_role(const char *newval, void *extra);
extern const char *show_role(void);
+extern bool check_password_duration(char **newval, void **extra, GucSource source);
+extern void assign_password_duration(const char *newval, void *extra);
+extern const char *show_password_duration(void);
+
#endif /* VARIABLE_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bf84593a61c..616ffe38fcb 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -163,6 +163,7 @@ PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("expires", EXPIRES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("expression", EXPRESSION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD, BARE_LABEL)
--
2.35.1
[application/x-patch] v2-0001-Move-rolpassword-out-of-pg_authid-into-a-new-tabl.patch (114.4K, ../../CAGB+Vh5Ed65gCmjJxCYguVoBoiDLePYDAeHMkb6-7PR-DFU-yQ@mail.gmail.com/3-v2-0001-Move-rolpassword-out-of-pg_authid-into-a-new-tabl.patch)
download | inline diff:
From 1d87319a60a1ed14ec6ea5c2eea80d95e70433bb Mon Sep 17 00:00:00 2001
From: Joshua Brindle <[email protected]>
Date: Thu, 9 Dec 2021 16:18:25 -0800
Subject: [PATCH v2 1/3] Move rolpassword out of pg_authid into a new table
In preparation for more flexibility in password
management the rolpassword column needs to be
moved into a new table.
Signed-off-by: Joshua Brindle <[email protected]>
---
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/catalog.c | 8 +-
src/backend/catalog/system_views.sql | 5 +-
src/backend/commands/user.c | 226 ++++++++++++----
src/backend/libpq/auth-sasl.c | 2 +-
src/backend/libpq/auth-scram.c | 4 +-
src/backend/libpq/auth.c | 16 ++
src/backend/libpq/crypt.c | 40 ++-
src/backend/utils/cache/catcache.c | 1 +
src/backend/utils/cache/relcache.c | 13 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/initdb/initdb.c | 2 +-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/common/scram-common.c | 2 +-
src/include/catalog/pg_auth_password.h | 50 ++++
src/include/catalog/pg_authid.dat | 26 +-
src/include/catalog/pg_authid.h | 9 +-
src/include/commands/user.h | 1 +
src/include/libpq/crypt.h | 2 +-
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_index.out | 14 +-
src/test/regress/expected/oidjoins.out | 1 +
src/test/regress/expected/password.out | 44 ++--
src/test/regress/expected/roleattributes.out | 256 +++++++++----------
src/test/regress/expected/rules.out | 5 +-
src/test/regress/expected/tablespace.out | 12 +-
src/test/regress/sql/create_index.sql | 10 +-
src/test/regress/sql/password.sql | 32 ++-
src/test/regress/sql/roleattributes.sql | 64 ++---
src/test/regress/sql/tablespace.sql | 8 +-
30 files changed, 563 insertions(+), 318 deletions(-)
create mode 100644 src/include/catalog/pg_auth_password.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 89a0221ec9b..450cfd9a589 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -63,7 +63,7 @@ CATALOG_HEADERS := \
pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
pg_database.h pg_db_role_setting.h pg_tablespace.h \
- pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
+ pg_authid.h pg_auth_members.h pg_auth_password.h pg_shdepend.h pg_shdescription.h \
pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 520f77971b3..11c149fd625 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -28,6 +28,7 @@
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
@@ -246,6 +247,7 @@ IsSharedRelation(Oid relationId)
{
/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
if (relationId == AuthIdRelationId ||
+ relationId == AuthPasswordRelationId ||
relationId == AuthMemRelationId ||
relationId == DatabaseRelationId ||
relationId == DbRoleSettingRelationId ||
@@ -260,6 +262,8 @@ IsSharedRelation(Oid relationId)
/* These are their indexes */
if (relationId == AuthIdOidIndexId ||
relationId == AuthIdRolnameIndexId ||
+ relationId == AuthPasswordRoleOidIndexId ||
+ relationId == AuthMemRoleMemIndexId ||
relationId == AuthMemMemRoleIndexId ||
relationId == AuthMemRoleMemIndexId ||
relationId == DatabaseNameIndexId ||
@@ -279,8 +283,8 @@ IsSharedRelation(Oid relationId)
relationId == TablespaceOidIndexId)
return true;
/* These are their toast tables and toast indexes */
- if (relationId == PgAuthidToastTable ||
- relationId == PgAuthidToastIndex ||
+ if (relationId == PgAuthPasswordToastTable ||
+ relationId == PgAuthPasswordToastIndex ||
relationId == PgDatabaseToastTable ||
relationId == PgDatabaseToastIndex ||
relationId == PgDbRoleSettingToastTable ||
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 0fc614e32c0..c1e42e6716f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -40,11 +40,14 @@ CREATE VIEW pg_shadow AS
rolsuper AS usesuper,
rolreplication AS userepl,
rolbypassrls AS usebypassrls,
- rolpassword AS passwd,
+ p.password AS passwd,
rolvaliduntil AS valuntil,
setconfig AS useconfig
FROM pg_authid LEFT JOIN pg_db_role_setting s
ON (pg_authid.oid = setrole AND setdatabase = 0)
+ LEFT JOIN pg_auth_password p
+ ON p.roleid = pg_authid.oid
+
WHERE rolcanlogin;
REVOKE ALL ON pg_shadow FROM public;
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index c263f6c8b9f..5b6ce37f1bf 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -23,6 +23,7 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
#include "commands/comment.h"
@@ -64,6 +65,42 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Is the role able to log in
+*/
+bool
+is_role_valid(const char *rolename, char **logdetail)
+{
+ HeapTuple roleTup;
+ Datum datum;
+ bool isnull;
+ TimestampTz vuntil = 0;
+
+ /* Get role info from pg_authid */
+ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename));
+ if (!HeapTupleIsValid(roleTup))
+ {
+ *logdetail = psprintf(_("Role \"%s\" does not exist."),
+ rolename);
+ return false; /* no such user */
+ }
+
+ datum = SysCacheGetAttr(AUTHNAME, roleTup,
+ Anum_pg_authid_rolvaliduntil, &isnull);
+ ReleaseSysCache(roleTup);
+
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ if (!isnull && vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."), rolename);
+ return false;
+ }
+
+ return true;
+}
+
/*
* CREATE ROLE
@@ -351,43 +388,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
-
- if (password)
- {
- char *shadow_pass;
- const char *logdetail = NULL;
-
- /*
- * Don't allow an empty password. Libpq treats an empty password the
- * same as no password at all, and won't even try to authenticate. But
- * other clients might, so allowing it would be confusing. By clearing
- * the password when an empty string is specified, the account is
- * consistently locked for all clients.
- *
- * Note that this only covers passwords stored in the database itself.
- * There are also checks in the authentication code, to forbid an
- * empty password from being used with authentication methods that
- * fetch the password from an external system, like LDAP or PAM.
- */
- if (password[0] == '\0' ||
- plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
- {
- ereport(NOTICE,
- (errmsg("empty string is not a valid password, clearing password")));
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
- }
- else
- {
- /* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, stmt->role,
- password);
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(shadow_pass);
- }
- }
- else
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -422,6 +422,60 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
*/
CatalogTupleInsert(pg_authid_rel, tuple);
+ if (password)
+ {
+ char *shadow_pass;
+ char *logdetail;
+ Datum new_password_record[Natts_pg_auth_password];
+ bool new_password_record_nulls[Natts_pg_auth_password];
+ Relation pg_auth_password_rel;
+ TupleDesc pg_auth_password_dsc;
+ HeapTuple new_tuple;
+
+ /*
+ * Don't allow an empty password. Libpq treats an empty password the
+ * same as no password at all, and won't even try to authenticate. But
+ * other clients might, so allowing it would be confusing. By clearing
+ * the password when an empty string is specified, the account is
+ * consistently locked for all clients.
+ *
+ * Note that this only covers passwords stored in the database itself.
+ * There are also checks in the authentication code, to forbid an
+ * empty password from being used with authentication methods that
+ * fetch the password from an external system, like LDAP or PAM.
+ */
+ if (password[0] == '\0' ||
+ plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
+ {
+ ereport(NOTICE,
+ (errmsg("empty string is not a valid password, clearing password")));
+ }
+ else
+ {
+ /* Encrypt the password to the requested format. */
+ shadow_pass = encrypt_password(Password_encryption, stmt->role,
+ password);
+
+ MemSet(new_password_record, 0, sizeof(new_password_record));
+ MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
+
+ /* open password table and insert it. */
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
+
+ new_password_record[Anum_pg_auth_password_password - 1] =
+ CStringGetTextDatum(shadow_pass);
+ new_password_record[Anum_pg_auth_password_roleid - 1] = roleid;
+
+ new_tuple = heap_form_tuple(pg_auth_password_dsc,
+ new_password_record, new_password_record_nulls);
+ CatalogTupleInsert(pg_auth_password_rel, new_tuple);
+ heap_freetuple(new_tuple);
+ table_close(pg_auth_password_rel, NoLock);
+
+ }
+ }
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -495,6 +549,9 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
Datum new_record[Natts_pg_authid];
bool new_record_nulls[Natts_pg_authid];
bool new_record_repl[Natts_pg_authid];
+ Datum new_password_record[Natts_pg_auth_password];
+ bool new_password_record_nulls[Natts_pg_auth_password];
+ bool new_password_record_repl[Natts_pg_auth_password];
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
HeapTuple tuple,
@@ -624,6 +681,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
rolename = pstrdup(NameStr(authform->rolname));
roleid = authform->oid;
+
/*
* To mess with a superuser or replication role in any way you gotta be
* superuser. We also insist on superuser to change the BYPASSRLS
@@ -694,6 +752,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
+ MemSet(new_password_record, 0, sizeof(new_password_record));
+ MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
+ MemSet(new_password_record_repl, false, sizeof(new_password_record_repl));
+
/*
* issuper/createrole/etc
@@ -752,24 +814,24 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
ereport(NOTICE,
(errmsg("empty string is not a valid password, clearing password")));
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
}
else
{
/* Encrypt the password to the requested format. */
shadow_pass = encrypt_password(Password_encryption, rolename,
password);
- new_record[Anum_pg_authid_rolpassword - 1] =
+ new_password_record[Anum_pg_auth_password_password - 1] =
CStringGetTextDatum(shadow_pass);
}
- new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
}
/* unset password */
if (dpassword && dpassword->arg == NULL)
{
- new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
}
/* valid until */
@@ -786,12 +848,48 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
new_record_nulls, new_record_repl);
CatalogTupleUpdate(pg_authid_rel, &tuple->t_self, new_tuple);
-
- InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
-
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
+ if (new_password_record_repl[Anum_pg_auth_password_password - 1] == true)
+ {
+ Relation pg_auth_password_rel;
+ TupleDesc pg_auth_password_dsc;
+ HeapTuple password_tuple;
+
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
+ password_tuple = SearchSysCache1(AUTHPASSWORD, ObjectIdGetDatum(roleid));
+
+ if (new_password_record_nulls[Anum_pg_auth_password_password - 1] == true) /* delete existing password */
+ {
+ if (HeapTupleIsValid(password_tuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &password_tuple->t_self);
+ ReleaseSysCache(password_tuple);
+ }
+ }
+ else if (HeapTupleIsValid(password_tuple)) /* update existing password */
+ {
+ new_tuple = heap_modify_tuple(password_tuple, pg_auth_password_dsc, new_password_record,
+ new_password_record_nulls, new_password_record_repl);
+ CatalogTupleUpdate(pg_auth_password_rel, &password_tuple->t_self, new_tuple);
+ ReleaseSysCache(password_tuple);
+ heap_freetuple(new_tuple);
+ }
+ else /* insert new password */
+ {
+ new_password_record[Anum_pg_auth_password_roleid - 1] = roleid;
+
+ new_tuple = heap_form_tuple(pg_auth_password_dsc,
+ new_password_record, new_password_record_nulls);
+ CatalogTupleInsert(pg_auth_password_rel, new_tuple);
+ heap_freetuple(new_tuple);
+ }
+
+ table_close(pg_auth_password_rel, NoLock);
+ }
+ InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -901,7 +999,6 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
return roleid;
}
-
/*
* DROP ROLE
*/
@@ -909,7 +1006,8 @@ void
DropRole(DropRoleStmt *stmt)
{
Relation pg_authid_rel,
- pg_auth_members_rel;
+ pg_auth_members_rel,
+ pg_auth_password_rel;
ListCell *item;
if (!have_createrole_privilege())
@@ -923,6 +1021,8 @@ DropRole(DropRoleStmt *stmt)
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_auth_members_rel = table_open(AuthMemRelationId, RowExclusiveLock);
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+
foreach(item, stmt->roles)
{
@@ -1056,6 +1156,15 @@ DropRole(DropRoleStmt *stmt)
DeleteSharedComments(roleid, AuthIdRelationId);
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
+ /*
+ * Drop password
+ */
+ tuple = SearchSysCache1(AUTHPASSWORD, roleid);
+ if (HeapTupleIsValid(tuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &tuple->t_self);
+ ReleaseSysCache(tuple);
+ }
+
/*
* Remove settings for this role.
*/
@@ -1077,7 +1186,9 @@ DropRole(DropRoleStmt *stmt)
* Now we can clean up; but keep locks until commit.
*/
table_close(pg_auth_members_rel, NoLock);
+ table_close(pg_auth_password_rel, NoLock);
table_close(pg_authid_rel, NoLock);
+
}
/*
@@ -1087,11 +1198,12 @@ ObjectAddress
RenameRole(const char *oldname, const char *newname)
{
HeapTuple oldtuple,
- newtuple;
+ newtuple,
+ passtuple;
TupleDesc dsc;
Relation rel;
Datum datum;
- bool isnull;
+ bool isnull = true;
Datum repl_val[Natts_pg_authid];
bool repl_null[Natts_pg_authid];
bool repl_repl[Natts_pg_authid];
@@ -1189,14 +1301,24 @@ RenameRole(const char *oldname, const char *newname)
CStringGetDatum(newname));
repl_null[Anum_pg_authid_rolname - 1] = false;
- datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
+ passtuple = SearchSysCache1(AUTHPASSWORD, roleid);
+
+ if (HeapTupleIsValid(passtuple))
+ datum = SysCacheGetAttr(AUTHPASSWORD, passtuple,
+ Anum_pg_auth_password_password, &isnull);
if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
{
+ Relation pg_auth_password_rel;
+
/* MD5 uses the username as salt, so just clear it on a rename */
- repl_repl[Anum_pg_authid_rolpassword - 1] = true;
- repl_null[Anum_pg_authid_rolpassword - 1] = true;
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ if (HeapTupleIsValid(passtuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
+ ReleaseSysCache(passtuple);
+ }
+ table_close(pg_auth_password_rel, NoLock);
ereport(NOTICE,
(errmsg("MD5 password cleared because of role rename")));
}
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index a1d7dbb6d58..805b3695b78 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -33,7 +33,7 @@
* implementation.
*
* shadow_pass is an optional pointer to the stored secret of the role
- * authenticated, from pg_authid.rolpassword. For mechanisms that use
+ * authenticated, from pg_auth_password.password. For mechanisms that use
* shadowed passwords, a NULL pointer here means that an entry could not
* be found for the role (or the user does not exist), and the mechanism
* should fail the authentication exchange.
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index ee7f52218ab..795f1cba555 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -220,7 +220,7 @@ scram_get_mechanisms(Port *port, StringInfo buf)
* It should be one of the mechanisms that we support, as returned by
* scram_get_mechanisms().
*
- * 'shadow_pass' is the role's stored secret, from pg_authid.rolpassword.
+ * 'shadow_pass' is the role's stored secret, from pg_auth_password.password.
* The username was provided by the client in the startup message, and is
* available in port->user_name. If 'shadow_pass' is NULL, we still perform
* an authentication exchange, but it will fail, as if an incorrect password
@@ -454,7 +454,7 @@ scram_exchange(void *opaq, const char *input, int inputlen,
}
/*
- * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
+ * Construct a SCRAM secret, for storing in pg_auth_password.password.
*
* The result is palloc'd, so caller is responsible for freeing it.
*/
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index efc53f31353..5cfde1eaa13 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -592,10 +592,26 @@ ClientAuthentication(Port *port)
case uaMD5:
case uaSCRAM:
+ /*
+ * check to be sure we are not past rolvaliduntil
+ */
+ if (!is_role_valid(port->user_name, &logdetail)) {
+ status = STATUS_ERROR;
+ break;
+ }
+
status = CheckPWChallengeAuth(port, &logdetail);
break;
case uaPassword:
+ /*
+ * check to be sure we are not past rolvaliduntil
+ */
+ if (!is_role_valid(port->user_name, &logdetail)) {
+ status = STATUS_ERROR;
+ break;
+ }
+
status = CheckPasswordAuth(port, &logdetail);
break;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 1ff8b0507d4..745e61034c2 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -2,7 +2,7 @@
*
* crypt.c
* Functions for dealing with encrypted passwords stored in
- * pg_authid.rolpassword.
+ * pg_auth_password.password.
*
* Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -16,6 +16,7 @@
#include <unistd.h>
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "common/md5.h"
#include "common/scram-common.h"
#include "libpq/crypt.h"
@@ -36,8 +37,7 @@
char *
get_role_password(const char *role, const char **logdetail)
{
- TimestampTz vuntil = 0;
- HeapTuple roleTup;
+ HeapTuple roleTup, passTup;
Datum datum;
bool isnull;
char *shadow_pass;
@@ -51,33 +51,29 @@ get_role_password(const char *role, const char **logdetail)
return NULL; /* no such user */
}
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolpassword, &isnull);
- if (isnull)
+ datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_oid, &isnull);
+
+ passTup = SearchSysCache1(AUTHPASSWORD, datum);
+
+ if (!HeapTupleIsValid(passTup))
{
- ReleaseSysCache(roleTup);
*logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
return NULL; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
-
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolvaliduntil, &isnull);
- if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ datum = SysCacheGetAttr(AUTHPASSWORD, passTup,
+ Anum_pg_auth_password_password, &isnull);
ReleaseSysCache(roleTup);
-
- /*
- * Password OK, but check to be sure we are not past rolvaliduntil
- */
- if (!isnull && vuntil < GetCurrentTimestamp())
+ if (isnull) /* this should not happen any more but just in case */
{
- *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ ReleaseSysCache(passTup);
+ *logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
- return NULL;
+ return NULL; /* user has no password */
}
+ shadow_pass = TextDatumGetCString(datum);
+ ReleaseSysCache(passTup);
return shadow_pass;
}
@@ -156,7 +152,7 @@ encrypt_password(PasswordType target_type, const char *role,
* Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password or password hash, as stored
- * in pg_authid.rolpassword.
+ * in pg_auth_password.password.
* 'client_pass' is the response given by the remote user to the MD5 challenge.
* 'md5_salt' is the salt used in the MD5 authentication challenge.
*
@@ -211,7 +207,7 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
* Check given password for given user, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password hash, as stored in
- * pg_authid.rolpassword.
+ * pg_auth_password.password.
* 'client_pass' is the password given by the remote user.
*
* In the error case, store a string at *logdetail that will be sent to the
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index ec073e1ed06..356e9cde6fb 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1104,6 +1104,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
+ case AUTHPASSWORD:
case AUTHMEMMEMROLE:
case DATABASEOID:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 43f14c233d6..d84cb883f51 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_attrdef.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
@@ -113,6 +114,7 @@ static const FormData_pg_attribute Desc_pg_proc[Natts_pg_proc] = {Schema_pg_proc
static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type};
static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
+static const FormData_pg_attribute Desc_pg_auth_password[Natts_pg_auth_password] = {Schema_pg_auth_password};
static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel};
@@ -3485,6 +3487,7 @@ RelationBuildLocalRelation(const char *relname,
{
case DatabaseRelationId:
case AuthIdRelationId:
+ case AuthPasswordRelationId:
case AuthMemRelationId:
case RelationRelationId:
case AttributeRelationId:
@@ -3905,7 +3908,7 @@ RelationCacheInitialize(void)
* RelationCacheInitializePhase2
*
* This is called to prepare for access to shared catalogs during startup.
- * We must at least set up nailed reldescs for pg_database, pg_authid,
+ * We must at least set up nailed reldescs for pg_database, pg_authid, pg_auth_password,
* pg_auth_members, and pg_shseclabel. Ideally we'd like to have reldescs
* for their indexes, too. We attempt to load this information from the
* shared relcache init file. If that's missing or broken, just make
@@ -3950,8 +3953,10 @@ RelationCacheInitializePhase2(void)
Natts_pg_shseclabel, Desc_pg_shseclabel);
formrdesc("pg_subscription", SubscriptionRelation_Rowtype_Id, true,
Natts_pg_subscription, Desc_pg_subscription);
+ formrdesc("pg_auth_password", AuthPasswordRelation_Rowtype_Id, true,
+ Natts_pg_auth_password, Desc_pg_auth_password);
-#define NUM_CRITICAL_SHARED_RELS 5 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS 6 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
@@ -4090,8 +4095,10 @@ RelationCacheInitializePhase3(void)
AuthMemRelationId);
load_critical_index(SharedSecLabelObjectIndexId,
SharedSecLabelRelationId);
+ load_critical_index(AuthPasswordRoleOidIndexId,
+ AuthPasswordRelationId);
-#define NUM_CRITICAL_SHARED_INDEXES 6 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 7 /* fix if you change list above */
criticalSharedRelcachesBuilt = true;
}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 1912b121463..de2b3ec1ebf 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -27,6 +27,7 @@
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
@@ -255,6 +256,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {AuthPasswordRelationId, /* AUTHPASSWORD */
+ AuthPasswordRoleOidIndexId,
+ 1,
+ {
+ Anum_pg_auth_password_roleid,
+ 0,
+ 0,
+ 0
+ },
+ 8
+ },
{
CastRelationId, /* CASTSOURCETARGET */
CastSourceTargetIndexId,
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index bbca1236d95..16349dc0510 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1452,7 +1452,7 @@ setup_auth(FILE *cmdfd)
* The authid table shouldn't be readable except through views, to
* ensure passwords are not publicly visible.
*/
- "REVOKE ALL ON pg_authid FROM public;\n\n",
+ "REVOKE ALL ON pg_auth_password FROM public;\n\n",
NULL
};
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 2dc33627630..fc282c8046d 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -759,7 +759,18 @@ dumpRoles(PGconn *conn)
int i;
/* note: rolconfig is dumped later */
- if (server_version >= 90600)
+ if (server_version >= 150000)
+ printfPQExpBuffer(buf,
+ "SELECT oid, rolname, rolsuper, rolinherit, "
+ "rolcreaterole, rolcreatedb, "
+ "rolcanlogin, rolconnlimit, p.password as rolpassword, "
+ "rolvaliduntil, rolreplication, rolbypassrls, "
+ "pg_catalog.shobj_description(oid, '%s') as rolcomment, "
+ "rolname = current_user AS is_current_user "
+ "FROM %s LEFT JOIN pg_auth_password p ON %s.oid = p.roleid "
+ "WHERE rolname !~ '^pg_' "
+ "ORDER BY 2", role_catalog, role_catalog, role_catalog);
+ else if (server_version >= 90600)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 12686259299..d9d0cba7379 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -181,7 +181,7 @@ scram_ServerKey(const uint8 *salted_password, uint8 *result,
/*
- * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
+ * Construct a SCRAM secret, for storing in pg_auth_password.password.
*
* The password should already have been processed with SASLprep, if necessary!
*
diff --git a/src/include/catalog/pg_auth_password.h b/src/include/catalog/pg_auth_password.h
new file mode 100644
index 00000000000..beaa2d40b90
--- /dev/null
+++ b/src/include/catalog/pg_auth_password.h
@@ -0,0 +1,50 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_password.h
+ * definition of the "authorization identifier" system catalog (pg_auth_password)
+ *
+ * Portions Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/pg_auth_password.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_PASSWORD_H
+#define PG_AUTH_PASSWORD_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_auth_password_d.h"
+
+/* ----------------
+ * pg_auth_password definition. cpp turns this into
+ * typedef struct FormData_pg_auth_password
+ * ----------------
+ */
+CATALOG(pg_auth_password,4548,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4549,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
+{
+ Oid roleid BKI_LOOKUP(pg_authid); /* ID of a role */
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text password; /* password */
+#endif
+} FormData_pg_auth_password;
+
+/* ----------------
+ * Form_pg_auth_password corresponds to a pointer to a tuple with
+ * the format of pg_auth_password relation.
+ * ----------------
+ */
+
+DECLARE_TOAST(pg_auth_password, 4175, 4176);
+#define PgAuthPasswordToastTable 4175
+#define PgAuthPasswordToastIndex 4176
+
+typedef FormData_pg_auth_password *Form_pg_auth_password;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4550, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops));
+
+
+#endif /* PG_AUTH_PASSWORD_H */
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 6c28119fa1a..da36375a582 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -23,66 +23,66 @@
rolname => 'POSTGRES', rolsuper => 't', rolinherit => 't',
rolcreaterole => 't', rolcreatedb => 't', rolcanlogin => 't',
rolreplication => 't', rolbypassrls => 't', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6171', oid_symbol => 'ROLE_PG_DATABASE_OWNER',
rolname => 'pg_database_owner', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6181', oid_symbol => 'ROLE_PG_READ_ALL_DATA',
rolname => 'pg_read_all_data', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6182', oid_symbol => 'ROLE_PG_WRITE_ALL_DATA',
rolname => 'pg_write_all_data', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3373', oid_symbol => 'ROLE_PG_MONITOR',
rolname => 'pg_monitor', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3374', oid_symbol => 'ROLE_PG_READ_ALL_SETTINGS',
rolname => 'pg_read_all_settings', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3375', oid_symbol => 'ROLE_PG_READ_ALL_STATS',
rolname => 'pg_read_all_stats', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3377', oid_symbol => 'ROLE_PG_STAT_SCAN_TABLES',
rolname => 'pg_stat_scan_tables', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4569', oid_symbol => 'ROLE_PG_READ_SERVER_FILES',
rolname => 'pg_read_server_files', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4570', oid_symbol => 'ROLE_PG_WRITE_SERVER_FILES',
rolname => 'pg_write_server_files', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4571', oid_symbol => 'ROLE_PG_EXECUTE_SERVER_PROGRAM',
rolname => 'pg_execute_server_program', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4200', oid_symbol => 'ROLE_PG_SIGNAL_BACKEND',
rolname => 'pg_signal_backend', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4544', oid_symbol => 'ROLE_PG_CHECKPOINTER',
rolname => 'pg_checkpointer', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
]
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index 4b65e39a1f9..0cb5675a33f 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -42,9 +42,8 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
int32 rolconnlimit; /* max connections allowed (-1=no limit) */
/* remaining fields may be null; use heap_getattr to read them! */
-#ifdef CATALOG_VARLEN /* variable-length fields start here */
- text rolpassword; /* password, if any */
- timestamptz rolvaliduntil; /* password expiration time, if any */
+#ifdef CATALOG_VARLEN
+ timestamptz rolvaliduntil BKI_FORCE_NULL; /* role expiration time, if any */
#endif
} FormData_pg_authid;
@@ -55,10 +54,6 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
*/
typedef FormData_pg_authid *Form_pg_authid;
-DECLARE_TOAST(pg_authid, 4175, 4176);
-#define PgAuthidToastTable 4175
-#define PgAuthidToastIndex 4176
-
DECLARE_UNIQUE_INDEX(pg_authid_rolname_index, 2676, AuthIdRolnameIndexId, on pg_authid using btree(rolname name_ops));
DECLARE_UNIQUE_INDEX_PKEY(pg_authid_oid_index, 2677, AuthIdOidIndexId, on pg_authid using btree(oid oid_ops));
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d3dd8303d28..3e1aae57854 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -24,6 +24,7 @@ typedef void (*check_password_hook_type) (const char *username, const char *shad
extern PGDLLIMPORT check_password_hook_type check_password_hook;
+extern bool is_role_valid(const char *rolename, char **logdetail);
extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 3238cf66d3a..b8ff8ccb417 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -21,7 +21,7 @@
* Plaintext passwords can be passed in by the user, in a CREATE/ALTER USER
* command. They will be encrypted to MD5 or SCRAM-SHA-256 format, before
* storing on-disk, so only MD5 and SCRAM-SHA-256 passwords should appear
- * in pg_authid.rolpassword. They are also the allowed values for the
+ * in pg_auth_password.password. They are also the allowed values for the
* password_encryption GUC.
*/
typedef enum PasswordType
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 4463ea66bea..c7bc648f46a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,6 +43,7 @@ enum SysCacheIdentifier
AUTHMEMROLEMEM,
AUTHNAME,
AUTHOID,
+ AUTHPASSWORD,
CASTSOURCETARGET,
CLAAMNAMENSP,
CLAOID,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index d55aec3a1d0..9875b277f26 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2515,10 +2515,10 @@ REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
ERROR: cannot reindex system catalogs concurrently
REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
ERROR: cannot reindex system catalogs concurrently
--- These are the toast table and index of pg_authid.
-REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
+-- These are the toast table and index of pg_database.
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1262; -- no catalog toast table
ERROR: cannot reindex system catalogs concurrently
-REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -- no catalog toast index
ERROR: cannot reindex system catalogs concurrently
REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
ERROR: cannot reindex system catalogs concurrently
@@ -2817,10 +2817,10 @@ ERROR: must be owner of schema schema_to_reindex
RESET ROLE;
GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
SET SESSION ROLE regress_reindexuser;
-REINDEX TABLE pg_toast.pg_toast_1260;
-ERROR: must be owner of table pg_toast_1260
-REINDEX INDEX pg_toast.pg_toast_1260_index;
-ERROR: must be owner of index pg_toast_1260_index
+REINDEX TABLE pg_toast.pg_toast_1262;
+ERROR: must be owner of table pg_toast_1262
+REINDEX INDEX pg_toast.pg_toast_1262_index;
+ERROR: must be owner of index pg_toast_1262_index
-- Clean up
RESET ROLE;
REVOKE USAGE ON SCHEMA pg_toast FROM regress_reindexuser;
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be3..b69a2a72a7b 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -197,6 +197,7 @@ NOTICE: checking pg_tablespace {spcowner} => pg_authid {oid}
NOTICE: checking pg_auth_members {roleid} => pg_authid {oid}
NOTICE: checking pg_auth_members {member} => pg_authid {oid}
NOTICE: checking pg_auth_members {grantor} => pg_authid {oid}
+NOTICE: checking pg_auth_password {roleid} => pg_authid {oid}
NOTICE: checking pg_shdepend {dbid} => pg_database {oid}
NOTICE: checking pg_shdepend {classid} => pg_class {oid}
NOTICE: checking pg_shdepend {refclassid} => pg_class {oid}
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 7c84c9da337..4ffc41a5455 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -24,10 +24,12 @@ CREATE ROLE regress_passwd4 PASSWORD NULL;
--
-- Since the salt is random, the exact value stored will be different on every test
-- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
rolname | rolpassword_masked
-----------------+---------------------------------------------------
regress_passwd1 | md5783277baca28003b33453252be4dbb34
@@ -40,12 +42,14 @@ SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+
ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new;
NOTICE: MD5 password cleared because of role rename
-- md5 entry should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd2_new'
- ORDER BY rolname, rolpassword;
- rolname | rolpassword
----------------------+-------------
+ ORDER BY rolname, password;
+ rolname | password
+---------------------+----------
regress_passwd2_new |
(1 row)
@@ -72,10 +76,12 @@ CREATE ROLE regress_passwd6 PASSWORD 'SCRAM-SHA-256$1234';
CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
-- invalid length
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
rolname | rolpassword_masked
-----------------+---------------------------------------------------
regress_passwd1 | md5cd3578025fe2c3d7ed1b9a9b26238b70
@@ -95,9 +101,11 @@ ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
NOTICE: empty string is not a valid password, clearing password
ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
NOTICE: empty string is not a valid password, clearing password
-SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
- rolpassword
--------------
+SELECT password FROM pg_authid
+LEFT JOIN pg_auth_password p
+ON pg_authid.oid = p.roleid WHERE rolname='regress_passwd_empty';
+ password
+----------
(1 row)
@@ -110,8 +118,10 @@ CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
-- Check that the invalid secrets were re-hashed. A re-hashed secret
-- should not contain the original salt.
-SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
+SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
rolname | is_rolpassword_rehashed
@@ -134,11 +144,13 @@ DROP ROLE regress_passwd_sha_len0;
DROP ROLE regress_passwd_sha_len1;
DROP ROLE regress_passwd_sha_len2;
-- all entries should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
- rolname | rolpassword
----------+-------------
+ ORDER BY rolname, password;
+ rolname | password
+---------+----------
(0 rows)
diff --git a/src/test/regress/expected/roleattributes.out b/src/test/regress/expected/roleattributes.out
index 5e6969b173e..18a9dacadde 100644
--- a/src/test/regress/expected/roleattributes.out
+++ b/src/test/regress/expected/roleattributes.out
@@ -1,233 +1,233 @@
-- default for superuser is false
CREATE ROLE regress_test_def_superuser;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_superuser | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
-- default for inherit is true
CREATE ROLE regress_test_def_inherit;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_inherit | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_inherit WITH INHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
-- default for create role is false
CREATE ROLE regress_test_def_createrole;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_createrole | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
-- default for create database is false
CREATE ROLE regress_test_def_createdb;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_createdb | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createdb WITH NOCREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
-- default for can login is false for role
CREATE ROLE regress_test_def_role_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_role_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
-- default for can login is true for user
CREATE USER regress_test_def_user_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_user_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
CREATE USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER USER regress_test_user_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
-- default for replication is false
CREATE ROLE regress_test_def_replication;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_replication | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_replication | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | t | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
ALTER ROLE regress_test_replication WITH NOREPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | t | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
-- default for bypassrls is false
CREATE ROLE regress_test_def_bypassrls;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_bypassrls | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
-- clean up roles
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index f29375c2a90..56bc581ab35 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1716,11 +1716,12 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
pg_authid.rolsuper AS usesuper,
pg_authid.rolreplication AS userepl,
pg_authid.rolbypassrls AS usebypassrls,
- pg_authid.rolpassword AS passwd,
+ p.password AS passwd,
pg_authid.rolvaliduntil AS valuntil,
s.setconfig AS useconfig
- FROM (pg_authid
+ FROM ((pg_authid
LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
+ LEFT JOIN pg_auth_password p ON ((p.roleid = pg_authid.oid)))
WHERE pg_authid.rolcanlogin;
pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
pg_get_shmem_allocations.off,
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c52cf1cfcf9..2ca2cd181f1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -53,13 +53,13 @@ ERROR: cannot move system relation "pg_authid_rolname_index"
REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
ERROR: cannot reindex system catalogs concurrently
-- toast relations, fail
-REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
-ERROR: cannot move system relation "pg_toast_1260_index"
-REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1260_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index;
+ERROR: cannot move system relation "pg_toast_1262_index"
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1262_index;
ERROR: cannot reindex system catalogs concurrently
-REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
-ERROR: cannot move system relation "pg_toast_1260_index"
-REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262;
+ERROR: cannot move system relation "pg_toast_1262_index"
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1262;
ERROR: cannot reindex system catalogs concurrently
-- system catalog, fail
REINDEX (TABLESPACE pg_global) TABLE pg_authid;
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index d8fded3d930..8d649f2355f 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -1068,9 +1068,9 @@ REINDEX TABLE CONCURRENTLY concur_reindex_tab;
COMMIT;
REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
--- These are the toast table and index of pg_authid.
-REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
-REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index
+-- These are the toast table and index of pg_database.
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1262; -- no catalog toast table
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -- no catalog toast index
REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
-- Warns about catalog relations
REINDEX SCHEMA CONCURRENTLY pg_catalog;
@@ -1243,8 +1243,8 @@ REINDEX SCHEMA schema_to_reindex;
RESET ROLE;
GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
SET SESSION ROLE regress_reindexuser;
-REINDEX TABLE pg_toast.pg_toast_1260;
-REINDEX INDEX pg_toast.pg_toast_1260_index;
+REINDEX TABLE pg_toast.pg_toast_1262;
+REINDEX INDEX pg_toast.pg_toast_1262_index;
-- Clean up
RESET ROLE;
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 98f49916e5d..bb0c815121f 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -23,18 +23,22 @@ CREATE ROLE regress_passwd4 PASSWORD NULL;
--
-- Since the salt is random, the exact value stored will be different on every test
-- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
-- Rename a role
ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new;
-- md5 entry should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd2_new'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
ALTER ROLE regress_passwd2_new RENAME TO regress_passwd2;
-- Change passwords with ALTER USER. With plaintext or already-encrypted
@@ -63,16 +67,20 @@ CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
-- invalid length
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
-- An empty password is not allowed, in any form
CREATE ROLE regress_passwd_empty PASSWORD '';
ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
-SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
+SELECT password FROM pg_authid
+LEFT JOIN pg_auth_password p
+ON pg_authid.oid = p.roleid WHERE rolname='regress_passwd_empty';
-- Test with invalid stored and server keys.
--
@@ -84,8 +92,10 @@ CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
-- Check that the invalid secrets were re-hashed. A re-hashed secret
-- should not contain the original salt.
-SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
+SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
@@ -103,7 +113,9 @@ DROP ROLE regress_passwd_sha_len1;
DROP ROLE regress_passwd_sha_len2;
-- all entries should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
diff --git a/src/test/regress/sql/roleattributes.sql b/src/test/regress/sql/roleattributes.sql
index c961b2d7303..09505c6a3be 100644
--- a/src/test/regress/sql/roleattributes.sql
+++ b/src/test/regress/sql/roleattributes.sql
@@ -1,83 +1,83 @@
-- default for superuser is false
CREATE ROLE regress_test_def_superuser;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
CREATE ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
ALTER ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
-- default for inherit is true
CREATE ROLE regress_test_def_inherit;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
CREATE ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
ALTER ROLE regress_test_inherit WITH INHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
ALTER ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
-- default for create role is false
CREATE ROLE regress_test_def_createrole;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
CREATE ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
ALTER ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
-- default for create database is false
CREATE ROLE regress_test_def_createdb;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
CREATE ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
ALTER ROLE regress_test_createdb WITH NOCREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
ALTER ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
-- default for can login is false for role
CREATE ROLE regress_test_def_role_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
CREATE ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
ALTER ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
-- default for can login is true for user
CREATE USER regress_test_def_user_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
CREATE USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
ALTER USER regress_test_user_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
ALTER USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
-- default for replication is false
CREATE ROLE regress_test_def_replication;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
CREATE ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
ALTER ROLE regress_test_replication WITH NOREPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
ALTER ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
-- default for bypassrls is false
CREATE ROLE regress_test_def_bypassrls;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
-- clean up roles
DROP ROLE regress_test_def_superuser;
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 21db433f2a8..d92135173fb 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -42,10 +42,10 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am;
REINDEX (TABLESPACE regress_tblspace) TABLE pg_authid;
REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
-- toast relations, fail
-REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
-REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1260_index;
-REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
-REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1262_index;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262;
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1262;
-- system catalog, fail
REINDEX (TABLESPACE pg_global) TABLE pg_authid;
REINDEX (TABLESPACE pg_global) TABLE CONCURRENTLY pg_authid;
--
2.35.1
[application/x-patch] v2-0002-multiple-passwords-work-with-scram-and-md5.patch (40.5K, ../../CAGB+Vh5Ed65gCmjJxCYguVoBoiDLePYDAeHMkb6-7PR-DFU-yQ@mail.gmail.com/4-v2-0002-multiple-passwords-work-with-scram-and-md5.patch)
download | inline diff:
From 446f4216b55db0b3ae1a90e46a9ef1983634e240 Mon Sep 17 00:00:00 2001
From: Joshua Brindle <[email protected]>
Date: Tue, 1 Feb 2022 14:32:55 -0800
Subject: [PATCH v2 2/3] multiple passwords work with scram and md5
also renaming roles, dropping roles, switching between
password_encryption
Signed-off-by: Joshua Brindle <[email protected]>
---
src/backend/commands/user.c | 199 +++++++++++++++++++++----
src/backend/libpq/auth-sasl.c | 4 +-
src/backend/libpq/auth-scram.c | 184 +++++++++++++----------
src/backend/libpq/auth.c | 94 +++++-------
src/backend/libpq/crypt.c | 51 ++++---
src/backend/parser/gram.y | 16 +-
src/backend/utils/cache/catcache.c | 2 +-
src/backend/utils/cache/syscache.c | 6 +-
src/include/catalog/pg_auth_password.h | 9 +-
src/include/commands/user.h | 2 +-
src/include/libpq/crypt.h | 2 +-
src/include/libpq/sasl.h | 4 +-
src/include/libpq/scram.h | 2 +-
src/include/parser/kwlist.h | 1 +
src/include/utils/syscache.h | 2 +-
src/test/regress/expected/password.out | 2 +-
16 files changed, 376 insertions(+), 204 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 5b6ce37f1bf..4117e58840a 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -18,6 +18,7 @@
#include "access/xact.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
+
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
@@ -31,11 +32,14 @@
#include "commands/defrem.h"
#include "commands/seclabel.h"
#include "commands/user.h"
+#include "common/scram-common.h"
#include "libpq/crypt.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+#include "utils/catcache.h"
#include "utils/fmgroids.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
@@ -47,6 +51,9 @@ Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
/* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
+/* default password name */
+const char* default_passname = "__def__";
+
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -69,7 +76,7 @@ have_createrole_privilege(void)
* Is the role able to log in
*/
bool
-is_role_valid(const char *rolename, char **logdetail)
+is_role_valid(const char *rolename, const char **logdetail)
{
HeapTuple roleTup;
Datum datum;
@@ -102,6 +109,64 @@ is_role_valid(const char *rolename, char **logdetail)
}
+static
+bool
+validate_and_get_salt(char *rolename, char **salt, const char **logdetail)
+{
+ char **current_secrets;
+ char *salt1, *salt2 = NULL;
+ int i, num;
+ PasswordType passtype;
+
+ if (Password_encryption == PASSWORD_TYPE_MD5)
+ {
+ *salt = rolename; /* md5 always uses role name, no need to look through the passwords */
+ return true;
+ }
+ else if (Password_encryption == PASSWORD_TYPE_PLAINTEXT)
+ {
+ *salt = NULL; /* Plaintext does not have a salt */
+ return true;
+ }
+
+ current_secrets = get_role_passwords(rolename, logdetail, &num);
+ if (num == 0)
+ {
+ *salt = NULL; /* No existing passwords, allow one to be generated */
+ return true;
+ }
+ for (i = 0; i < num; i++) {
+ passtype = get_password_type(current_secrets[i]);
+ if (passtype == PASSWORD_TYPE_MD5 || passtype == PASSWORD_TYPE_PLAINTEXT)
+ continue; /* md5 uses rolename as salt so it is always the same and plaintext has no salt */
+ else if (passtype == PASSWORD_TYPE_SCRAM_SHA_256) {
+ int iterations;
+ uint8 stored_key[SCRAM_KEY_LEN];
+ uint8 server_key[SCRAM_KEY_LEN];
+
+ parse_scram_secret(current_secrets[i], &iterations, &salt1,
+ stored_key, server_key);
+
+ if (salt2 != NULL) {
+ if (strcmp(salt1, salt2)) {
+ *logdetail = psprintf(_("inconsistent salts, clearing password"));
+ *salt = NULL;
+ return false;
+ }
+ }
+ else
+ salt2 = salt1;
+ }
+
+ }
+ for (i = 0; i < num; i++)
+ pfree(current_secrets[i]);
+ pfree(current_secrets);
+ if (salt2)
+ *salt = pstrdup(salt2);
+ return true;
+}
+
/*
* CREATE ROLE
*/
@@ -117,6 +182,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
+ char *passname = NULL; /* name of the password for managing multiple passwords */
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -144,6 +210,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ DefElem *dpassName = NULL;
+
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -246,6 +314,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
+ else if (strcmp(defel->defname, "passname") == 0)
+ {
+ if (dpassName)
+ errorConflictingDefElem(defel, pstate);
+ dpassName = defel;
+ }
+
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -283,6 +358,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
validUntil = strVal(dvalidUntil->arg);
if (dbypassRLS)
bypassrls = boolVal(dbypassRLS->arg);
+ if (dpassName)
+ passname = strVal(dpassName->arg);
/* Check some permissions first */
if (issuper)
@@ -424,8 +501,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- char *shadow_pass;
- char *logdetail;
+ char *shadow_pass, *salt;
+ const char *logdetail;
Datum new_password_record[Natts_pg_auth_password];
bool new_password_record_nulls[Natts_pg_auth_password];
Relation pg_auth_password_rel;
@@ -453,12 +530,18 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
else
{
/* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, stmt->role,
+ validate_and_get_salt(stmt->role, &salt, &logdetail);
+ shadow_pass = encrypt_password(Password_encryption, salt,
password);
MemSet(new_password_record, 0, sizeof(new_password_record));
MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
-
+ if (passname != NULL)
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(passname));
+ else
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(default_passname));
/* open password table and insert it. */
pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
@@ -560,6 +643,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ListCell *option;
char *rolename;
char *password = NULL; /* user password */
+ char *passname = NULL; /* password name for managing multiple passwords */
int connlimit = -1; /* maximum connections allowed */
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
@@ -575,6 +659,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ DefElem *dpassName = NULL;
Oid roleid;
check_rolespec_name(stmt->role,
@@ -652,6 +737,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
+ else if (strcmp(defel->defname, "passname") == 0)
+ {
+ if (dpassName)
+ errorConflictingDefElem(defel, pstate);
+ dpassName = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -669,6 +760,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
}
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
+ if (dpassName)
+ passname = strVal(dpassName->arg);
/*
* Scan the pg_authid relation to be certain the user exists.
@@ -805,8 +898,9 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
/* password */
if (password)
{
- char *shadow_pass;
- const char *logdetail = NULL;
+ char *salt = NULL;
+ const char *logdetail = NULL;
+ char *shadow_pass;
/* Like in CREATE USER, don't allow an empty password. */
if (password[0] == '\0' ||
@@ -818,13 +912,26 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
}
else
{
- /* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, rolename,
- password);
- new_password_record[Anum_pg_auth_password_password - 1] =
- CStringGetTextDatum(shadow_pass);
+ if (!validate_and_get_salt(rolename, &salt, &logdetail))
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
+ else
+ {
+ /* Encrypt the password to the requested format. */
+ shadow_pass = encrypt_password(Password_encryption, salt,
+ password);
+ new_password_record[Anum_pg_auth_password_password - 1] =
+ CStringGetTextDatum(shadow_pass);
+
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
+
+ if (passname)
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(passname));
+ else
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(default_passname));
+ }
}
- new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
}
/* unset password */
@@ -859,7 +966,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
- password_tuple = SearchSysCache1(AUTHPASSWORD, ObjectIdGetDatum(roleid));
+ if (dpassName)
+ password_tuple = SearchSysCache2(AUTHPASSWORDNAME, ObjectIdGetDatum(roleid), CStringGetDatum(passname));
+ else
+ password_tuple = SearchSysCache2(AUTHPASSWORDNAME, ObjectIdGetDatum(roleid), CStringGetDatum(default_passname));
if (new_password_record_nulls[Anum_pg_auth_password_password - 1] == true) /* delete existing password */
{
@@ -1157,14 +1267,23 @@ DropRole(DropRoleStmt *stmt)
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
/*
- * Drop password
+ * Drop password(s)
*/
- tuple = SearchSysCache1(AUTHPASSWORD, roleid);
- if (HeapTupleIsValid(tuple)) {
- CatalogTupleDelete(pg_auth_password_rel, &tuple->t_self);
- ReleaseSysCache(tuple);
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_password_roleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ sscan = systable_beginscan(pg_auth_password_rel, AuthPasswordRoleOidIndexId,
+ true, NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+ {
+ CatalogTupleDelete(pg_auth_password_rel, &tmp_tuple->t_self);
}
+ systable_endscan(sscan);
+
/*
* Remove settings for this role.
*/
@@ -1201,6 +1320,9 @@ RenameRole(const char *oldname, const char *newname)
newtuple,
passtuple;
TupleDesc dsc;
+ ScanKeyData scankey;
+ SysScanDesc sscan;
+
Relation rel;
Datum datum;
bool isnull = true;
@@ -1211,6 +1333,7 @@ RenameRole(const char *oldname, const char *newname)
Oid roleid;
ObjectAddress address;
Form_pg_authid authform;
+ Relation pg_auth_password_rel;
rel = table_open(AuthIdRelationId, RowExclusiveLock);
dsc = RelationGetDescr(rel);
@@ -1301,28 +1424,38 @@ RenameRole(const char *oldname, const char *newname)
CStringGetDatum(newname));
repl_null[Anum_pg_authid_rolname - 1] = false;
- passtuple = SearchSysCache1(AUTHPASSWORD, roleid);
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_password_roleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
- if (HeapTupleIsValid(passtuple))
- datum = SysCacheGetAttr(AUTHPASSWORD, passtuple,
- Anum_pg_auth_password_password, &isnull);
+ sscan = systable_beginscan(pg_auth_password_rel, AuthPasswordRoleOidIndexId,
+ true, NULL, 1, &scankey);
- if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
+ while (HeapTupleIsValid(passtuple = systable_getnext(sscan)))
{
- Relation pg_auth_password_rel;
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, passtuple,
+ Anum_pg_auth_password_password, &isnull);
- /* MD5 uses the username as salt, so just clear it on a rename */
- pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
+ {
- if (HeapTupleIsValid(passtuple)) {
- CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
- ReleaseSysCache(passtuple);
+ /* MD5 uses the username as salt, so just clear it on a rename */
+
+ if (HeapTupleIsValid(passtuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
+ ereport(NOTICE,
+ (errmsg("MD5 password cleared because of role rename")));
+
+ }
}
- table_close(pg_auth_password_rel, NoLock);
- ereport(NOTICE,
- (errmsg("MD5 password cleared because of role rename")));
}
+ systable_endscan(sscan);
+ table_close(pg_auth_password_rel, NoLock);
+
newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
CatalogTupleUpdate(rel, &oldtuple->t_self, newtuple);
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index 805b3695b78..652c18fa94a 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -49,7 +49,7 @@
* should just pass NULL.
*/
int
-CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
+CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, const char **passwords, int num,
const char **logdetail)
{
StringInfoData sasl_mechs;
@@ -136,7 +136,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
* This is because we don't want to reveal to an attacker what
* usernames are valid, nor which users have a valid password.
*/
- opaq = mech->init(port, selected_mech, shadow_pass);
+ opaq = mech->init(port, selected_mech, passwords, num);
inputlen = pq_getmsgint(&buf, 4);
if (inputlen == -1)
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 795f1cba555..95db4aaf958 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -109,7 +109,7 @@
static void scram_get_mechanisms(Port *port, StringInfo buf);
static void *scram_init(Port *port, const char *selected_mech,
- const char *shadow_pass);
+ const char **secrets, const int num_secrets);
static int scram_exchange(void *opaq, const char *input, int inputlen,
char **output, int *outputlen,
const char **logdetail);
@@ -132,6 +132,13 @@ typedef enum
SCRAM_AUTH_FINISHED
} scram_state_enum;
+typedef struct
+{
+ uint8 StoredKey[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+} scram_secret;
+
+
typedef struct
{
scram_state_enum state;
@@ -141,10 +148,16 @@ typedef struct
Port *port;
bool channel_binding_in_use;
+ /*
+ * The salt and iterations must be the same for all
+ * secrets since they are sent as part of the initial message
+ */
int iterations;
char *salt; /* base64-encoded */
- uint8 StoredKey[SCRAM_KEY_LEN];
- uint8 ServerKey[SCRAM_KEY_LEN];
+ /* Array of possible secrets */
+ scram_secret *secrets;
+ int num_secrets;
+ int chosen_secret; /* secret chosen during final client message */
/* Fields of the first message from client */
char cbind_flag;
@@ -220,17 +233,20 @@ scram_get_mechanisms(Port *port, StringInfo buf)
* It should be one of the mechanisms that we support, as returned by
* scram_get_mechanisms().
*
- * 'shadow_pass' is the role's stored secret, from pg_auth_password.password.
+ * 'passwords' are the role's stored secrets, from pg_auth_password.password.
* The username was provided by the client in the startup message, and is
* available in port->user_name. If 'shadow_pass' is NULL, we still perform
* an authentication exchange, but it will fail, as if an incorrect password
* was given.
*/
static void *
-scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
+scram_init(Port *port, const char *selected_mech, const char **secrets, const int num_secrets)
{
scram_state *state;
- bool got_secret;
+ bool got_secret = false;
+ int i;
+ int iterations;
+ char *salt = NULL; /* base64-encoded */
state = (scram_state *) palloc0(sizeof(scram_state));
state->port = port;
@@ -260,46 +276,48 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
/*
* Parse the stored secret.
*/
- if (shadow_pass)
+ if (secrets)
{
- int password_type = get_password_type(shadow_pass);
-
- if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
+ state->secrets = palloc0(sizeof(scram_secret) * num_secrets);
+ state->num_secrets = num_secrets;
+ for (i = 0; i < num_secrets; i++)
{
- if (parse_scram_secret(shadow_pass, &state->iterations, &state->salt,
- state->StoredKey, state->ServerKey))
- got_secret = true;
- else
+ int password_type = get_password_type(secrets[i]);
+
+ if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
{
- /*
- * The password looked like a SCRAM secret, but could not be
- * parsed.
- */
- ereport(LOG,
- (errmsg("invalid SCRAM secret for user \"%s\"",
- state->port->user_name)));
- got_secret = false;
+
+ if (parse_scram_secret(secrets[i], &state->iterations, &state->salt,
+ state->secrets[i].StoredKey, state->secrets[i].ServerKey))
+ {
+ if (salt) {
+ /* The stored iterations and salt must match or we cannot proceed, allow failure via mock */
+ if (strcmp(salt, state->salt) || iterations != state->iterations) {
+ ereport(WARNING, (errmsg("inconsistent salt or iterations for user \"%s\"",
+ state->port->user_name)));
+ got_secret = false; /* fail and allow mock creditials to be created */
+ break;
+ }
+ }
+ else
+ {
+ salt = state->salt;
+ iterations = state->iterations;
+ got_secret = true; /* We got at least one good SCRAM secret */
+ }
+ }
+ else
+ {
+ /*
+ * The password looked like a SCRAM secret, but could not be
+ * parsed.
+ */
+ ereport(LOG,
+ (errmsg("invalid SCRAM secret for user \"%s\"",
+ state->port->user_name)));
+ }
}
}
- else
- {
- /*
- * The user doesn't have SCRAM secret. (You cannot do SCRAM
- * authentication with an MD5 hash.)
- */
- state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM secret."),
- state->port->user_name);
- got_secret = false;
- }
- }
- else
- {
- /*
- * The caller requested us to perform a dummy authentication. This is
- * considered normal, since the caller requested it, so don't set log
- * detail.
- */
- got_secret = false;
}
/*
@@ -310,8 +328,10 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
*/
if (!got_secret)
{
+ state->secrets = palloc0(sizeof(scram_secret));
+
mock_scram_secret(state->port->user_name, &state->iterations,
- &state->salt, state->StoredKey, state->ServerKey);
+ &state->salt, state->secrets[0].StoredKey, state->secrets[0].ServerKey);
state->doomed = true;
}
@@ -459,7 +479,7 @@ scram_exchange(void *opaq, const char *input, int inputlen,
* The result is palloc'd, so caller is responsible for freeing it.
*/
char *
-pg_be_scram_build_secret(const char *password)
+pg_be_scram_build_secret(const char *password, const char *salt)
{
char *prep_password;
pg_saslprep_rc rc;
@@ -476,12 +496,14 @@ pg_be_scram_build_secret(const char *password)
if (rc == SASLPREP_SUCCESS)
password = (const char *) prep_password;
- /* Generate random salt */
- if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
+ /* Use passed in salt or generate random salt */
+ if (!salt && !pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate random salt")));
-
+ else if (salt)
+ pg_b64_decode(salt, strlen(salt), saltbuf, SCRAM_DEFAULT_SALT_LEN);
+
result = scram_build_secret(saltbuf, SCRAM_DEFAULT_SALT_LEN,
SCRAM_DEFAULT_ITERATIONS, password,
&errstr);
@@ -1114,47 +1136,57 @@ verify_client_proof(scram_state *state)
uint8 ClientSignature[SCRAM_KEY_LEN];
uint8 ClientKey[SCRAM_KEY_LEN];
uint8 client_StoredKey[SCRAM_KEY_LEN];
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
- int i;
+ pg_hmac_ctx *ctx;
+ int i, j;
const char *errstr = NULL;
-
/*
* Calculate ClientSignature. Note that we don't log directly a failure
* here even when processing the calculations as this could involve a mock
* authentication.
*/
- if (pg_hmac_init(ctx, state->StoredKey, SCRAM_KEY_LEN) < 0 ||
- pg_hmac_update(ctx,
- (uint8 *) state->client_first_message_bare,
- strlen(state->client_first_message_bare)) < 0 ||
- pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
- pg_hmac_update(ctx,
- (uint8 *) state->server_first_message,
- strlen(state->server_first_message)) < 0 ||
- pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
- pg_hmac_update(ctx,
- (uint8 *) state->client_final_message_without_proof,
- strlen(state->client_final_message_without_proof)) < 0 ||
- pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+ for (j = 0; j < state->num_secrets; j++)
{
- elog(ERROR, "could not calculate client signature: %s",
- pg_hmac_error(ctx));
- }
+ ctx = pg_hmac_create(PG_SHA256);
+ elog(LOG, "Trying to verify password %d", j);
+
+ if (pg_hmac_init(ctx, state->secrets[j].StoredKey, SCRAM_KEY_LEN) < 0 ||
+ pg_hmac_update(ctx,
+ (uint8 *) state->client_first_message_bare,
+ strlen(state->client_first_message_bare)) < 0 ||
+ pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
+ pg_hmac_update(ctx,
+ (uint8 *) state->server_first_message,
+ strlen(state->server_first_message)) < 0 ||
+ pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
+ pg_hmac_update(ctx,
+ (uint8 *) state->client_final_message_without_proof,
+ strlen(state->client_final_message_without_proof)) < 0 ||
+ pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+ {
+ elog(LOG, "could not calculate client signature");
+ pg_hmac_free(ctx);
+ continue;
+ }
- pg_hmac_free(ctx);
+ elog(LOG, "success on %d", j);
- /* Extract the ClientKey that the client calculated from the proof */
- for (i = 0; i < SCRAM_KEY_LEN; i++)
- ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+ pg_hmac_free(ctx);
- /* Hash it one more time, and compare with StoredKey */
- if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
- elog(ERROR, "could not hash stored key: %s", errstr);
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
- if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
- return false;
+ /* Hash it one more time, and compare with StoredKey */
+ if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
+ elog(ERROR, "could not hash stored key: %s", errstr);
- return true;
+ if (memcmp(client_StoredKey, state->secrets[j].StoredKey, SCRAM_KEY_LEN) == 0) {
+ elog(LOG, "Moving forward with Password %d", j);
+ state->chosen_secret = j;
+ return true;
+ }
+ }
+
+ return false;
}
/*
@@ -1380,7 +1412,7 @@ build_server_final_message(scram_state *state)
pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
/* calculate ServerSignature */
- if (pg_hmac_init(ctx, state->ServerKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, state->secrets[state->chosen_secret].ServerKey, SCRAM_KEY_LEN) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 5cfde1eaa13..5569599ab31 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -58,8 +58,7 @@ static void set_authn_id(Port *port, const char *id);
static int CheckPasswordAuth(Port *port, const char **logdetail);
static int CheckPWChallengeAuth(Port *port, const char **logdetail);
-static int CheckMD5Auth(Port *port, char *shadow_pass,
- const char **logdetail);
+static int CheckMD5Auth(Port *port, const char **passwords, int num, const char **logdetail);
/*----------------------------------------------------------------
@@ -790,8 +789,9 @@ static int
CheckPasswordAuth(Port *port, const char **logdetail)
{
char *passwd;
- int result;
- char *shadow_pass;
+ int result = STATUS_ERROR;
+ int i, num;
+ char **passwords;
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
@@ -799,17 +799,21 @@ CheckPasswordAuth(Port *port, const char **logdetail)
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
- shadow_pass = get_role_password(port->user_name, logdetail);
- if (shadow_pass)
- {
- result = plain_crypt_verify(port->user_name, shadow_pass, passwd,
- logdetail);
- }
- else
- result = STATUS_ERROR;
+ passwords = get_role_passwords(port->user_name, logdetail, &num);
+ if (passwords != NULL) {
+ for (i = 0; i < num; i++)
+ {
+ result = plain_crypt_verify(port->user_name, passwords[i], passwd,
+ logdetail);
+ if (result == STATUS_OK)
+ break; /* Found a matching password, no need to try any others */
+ }
+ for (i = 0; passwords[i] != NULL; i++)
+ pfree(passwords[i]);
- if (shadow_pass)
- pfree(shadow_pass);
+ pfree(passwords);
+ }
+
pfree(passwd);
if (result == STATUS_OK)
@@ -824,29 +828,15 @@ CheckPasswordAuth(Port *port, const char **logdetail)
static int
CheckPWChallengeAuth(Port *port, const char **logdetail)
{
- int auth_result;
- char *shadow_pass;
- PasswordType pwtype;
+ int auth_result = STATUS_ERROR;
+ int i, num;
+ char **passwords;
Assert(port->hba->auth_method == uaSCRAM ||
port->hba->auth_method == uaMD5);
- /* First look up the user's password. */
- shadow_pass = get_role_password(port->user_name, logdetail);
-
- /*
- * If the user does not exist, or has no password or it's expired, we
- * still go through the motions of authentication, to avoid revealing to
- * the client that the user didn't exist. If 'md5' is allowed, we choose
- * whether to use 'md5' or 'scram-sha-256' authentication based on current
- * password_encryption setting. The idea is that most genuine users
- * probably have a password of that type, and if we pretend that this user
- * had a password of that type, too, it "blends in" best.
- */
- if (!shadow_pass)
- pwtype = Password_encryption;
- else
- pwtype = get_password_type(shadow_pass);
+ /* First look up the user's passwords. */
+ passwords = get_role_passwords(port->user_name, logdetail, &num);
/*
* If 'md5' authentication is allowed, decide whether to perform 'md5' or
@@ -858,23 +848,17 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
* had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will
* fail.
*/
- if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5)
- auth_result = CheckMD5Auth(port, shadow_pass, logdetail);
- else
- auth_result = CheckSASLAuth(&pg_be_scram_mech, port, shadow_pass,
- logdetail);
+ if (passwords != NULL) {
+ if (port->hba->auth_method == uaMD5)
+ auth_result = CheckMD5Auth(port, (const char **) passwords, num, logdetail);
+ else
+ auth_result = CheckSASLAuth(&pg_be_scram_mech, port, (const char **) passwords, num,
+ logdetail);
- if (shadow_pass)
- pfree(shadow_pass);
+ for (i = 0; i < num; i++)
+ pfree(passwords[i]);
- /*
- * If get_role_password() returned error, return error, even if the
- * authentication succeeded.
- */
- if (!shadow_pass)
- {
- Assert(auth_result != STATUS_OK);
- return STATUS_ERROR;
+ pfree(passwords);
}
if (auth_result == STATUS_OK)
@@ -884,11 +868,12 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
}
static int
-CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
+CheckMD5Auth(Port *port, const char **passwords, int num, const char **logdetail)
{
char md5Salt[4]; /* Password salt */
char *passwd;
int result;
+ int i;
if (Db_user_namespace)
ereport(FATAL,
@@ -909,12 +894,13 @@ CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
- if (shadow_pass)
- result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
+ for (i = 0; i < num; i++)
+ {
+ result = md5_crypt_verify(port->user_name, passwords[i], passwd,
md5Salt, 4, logdetail);
- else
- result = STATUS_ERROR;
-
+ if (result == STATUS_OK)
+ break;
+ }
pfree(passwd);
return result;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 745e61034c2..09b6bebf40c 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -23,6 +23,7 @@
#include "libpq/scram.h"
#include "miscadmin.h"
#include "utils/builtins.h"
+#include "utils/catcache.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
@@ -34,13 +35,15 @@
* for the postmaster log, in *logdetail. The error reason should *not* be
* sent to the client, to avoid giving away user information!
*/
-char *
-get_role_password(const char *role, const char **logdetail)
+char **
+get_role_passwords(const char *role, const char **logdetail, int *num)
{
- HeapTuple roleTup, passTup;
+ HeapTuple roleTup;
Datum datum;
bool isnull;
- char *shadow_pass;
+ char **passwords;
+ CatCList *passlist;
+ int i;
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
@@ -52,30 +55,34 @@ get_role_password(const char *role, const char **logdetail)
}
datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_oid, &isnull);
-
- passTup = SearchSysCache1(AUTHPASSWORD, datum);
+ ReleaseSysCache(roleTup);
+ /* Find any existing password that is not the one being updated to get the salt */
+ passlist = SearchSysCacheList1(AUTHPASSWORDNAME, datum);
+ *num = passlist->n_members;
- if (!HeapTupleIsValid(passTup))
+ if (passlist->n_members == 0)
{
+ *num = 0;
*logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
+ ReleaseCatCacheList(passlist);
return NULL; /* user has no password */
}
- datum = SysCacheGetAttr(AUTHPASSWORD, passTup,
- Anum_pg_auth_password_password, &isnull);
- ReleaseSysCache(roleTup);
- if (isnull) /* this should not happen any more but just in case */
+ passwords = palloc0(sizeof(char *) * passlist->n_members);
+
+ for (i = 0; i < passlist->n_members; i++)
{
- ReleaseSysCache(passTup);
- *logdetail = psprintf(_("User \"%s\" has no password assigned."),
- role);
- return NULL; /* user has no password */
+ HeapTuple tup = &passlist->members[i]->tuple;
+
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_password, &isnull);
+ passwords[i] = TextDatumGetCString(datum);
}
- shadow_pass = TextDatumGetCString(datum);
- ReleaseSysCache(passTup);
- return shadow_pass;
+ ReleaseCatCacheList(passlist);
+
+ return passwords;
}
/*
@@ -107,7 +114,7 @@ get_password_type(const char *shadow_pass)
* hash, so it is stored as it is regardless of the requested type.
*/
char *
-encrypt_password(PasswordType target_type, const char *role,
+encrypt_password(PasswordType target_type, const char *salt,
const char *password)
{
PasswordType guessed_type = get_password_type(password);
@@ -128,13 +135,13 @@ encrypt_password(PasswordType target_type, const char *role,
case PASSWORD_TYPE_MD5:
encrypted_password = palloc(MD5_PASSWD_LEN + 1);
- if (!pg_md5_encrypt(password, role, strlen(role),
+ if (!pg_md5_encrypt(password, salt, strlen(salt),
encrypted_password, &errstr))
- elog(ERROR, "password encryption failed: %s", errstr);
+ elog(ERROR, "password encryption failed %s", errstr);
return encrypted_password;
case PASSWORD_TYPE_SCRAM_SHA_256:
- return pg_be_scram_build_secret(password);
+ return pg_be_scram_build_secret(password, salt);
case PASSWORD_TYPE_PLAINTEXT:
elog(ERROR, "cannot encrypt password with 'plaintext'");
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c9941d9cb4f..c3cb6c76aa5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -826,8 +826,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
- PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PATH
- PLACING PLAN PLANS POLICY
+
+ PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSNAME PASSWORD
+ PATH PLACING PLAN PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
@@ -1256,6 +1257,15 @@ AlterOptRoleElem:
errhint("Remove UNENCRYPTED to store the password in encrypted form instead."),
parser_errposition(@1)));
}
+ | PASSNAME Sconst
+ {
+ $$ = makeDefElem("passname",
+ (Node *)makeString($2), @1);
+ }
+ | VALID FOR Sconst
+ {
+ $$ = makeDefElem("validFor", (Node *)makeString($3), @1);
+ }
| INHERIT
{
$$ = makeDefElem("inherit", (Node *)makeBoolean(true), @1);
@@ -17082,6 +17092,7 @@ unreserved_keyword:
| PARTIAL
| PARTITION
| PASSING
+ | PASSNAME
| PASSWORD
| PATH
| PLAN
@@ -17700,6 +17711,7 @@ bare_label_keyword:
| PARTIAL
| PARTITION
| PASSING
+ | PASSNAME
| PASSWORD
| PATH
| PLACING
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 356e9cde6fb..c421ac0b19f 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1104,7 +1104,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
- case AUTHPASSWORD:
+ case AUTHPASSWORDNAME:
case AUTHMEMMEMROLE:
case DATABASEOID:
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index de2b3ec1ebf..9879120bf76 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -256,12 +256,12 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
- {AuthPasswordRelationId, /* AUTHPASSWORD */
+ {AuthPasswordRelationId, /* AUTHPASSWORDNAME */
AuthPasswordRoleOidIndexId,
- 1,
+ 2,
{
Anum_pg_auth_password_roleid,
- 0,
+ Anum_pg_auth_password_name,
0,
0
},
diff --git a/src/include/catalog/pg_auth_password.h b/src/include/catalog/pg_auth_password.h
index beaa2d40b90..4181caad30b 100644
--- a/src/include/catalog/pg_auth_password.h
+++ b/src/include/catalog/pg_auth_password.h
@@ -24,11 +24,13 @@
* typedef struct FormData_pg_auth_password
* ----------------
*/
-CATALOG(pg_auth_password,4548,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4549,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
+CATALOG(pg_auth_password,4551,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4552,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
{
Oid roleid BKI_LOOKUP(pg_authid); /* ID of a role */
+ NameData name; /* name of password for multiple password support */
+
#ifdef CATALOG_VARLEN /* variable-length fields start here */
- text password; /* password */
+ text password BKI_FORCE_NOT_NULL; /* password */
#endif
} FormData_pg_auth_password;
@@ -44,7 +46,6 @@ DECLARE_TOAST(pg_auth_password, 4175, 4176);
typedef FormData_pg_auth_password *Form_pg_auth_password;
-DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4550, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops));
-
+DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4553, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops, name name_ops));
#endif /* PG_AUTH_PASSWORD_H */
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 3e1aae57854..1c14f60a05b 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -24,7 +24,7 @@ typedef void (*check_password_hook_type) (const char *username, const char *shad
extern PGDLLIMPORT check_password_hook_type check_password_hook;
-extern bool is_role_valid(const char *rolename, char **logdetail);
+extern bool is_role_valid(const char *rolename, const char **logdetail);
extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index b8ff8ccb417..84c9c4b4f48 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -35,7 +35,7 @@ extern PasswordType get_password_type(const char *shadow_pass);
extern char *encrypt_password(PasswordType target_type, const char *role,
const char *password);
-extern char *get_role_password(const char *role, const char **logdetail);
+extern char **get_role_passwords(const char *role, const char **logdetail, int *num);
extern int md5_crypt_verify(const char *role, const char *shadow_pass,
const char *client_pass, const char *md5_salt,
diff --git a/src/include/libpq/sasl.h b/src/include/libpq/sasl.h
index 39ccf8f0e31..27b4e570333 100644
--- a/src/include/libpq/sasl.h
+++ b/src/include/libpq/sasl.h
@@ -77,7 +77,7 @@ typedef struct pg_be_sasl_mech
* disclosing valid user names.
*---------
*/
- void *(*init) (Port *port, const char *mech, const char *shadow_pass);
+ void *(*init) (Port *port, const char *mech, const char **secrets, const int num_secrets);
/*---------
* exchange()
@@ -131,6 +131,6 @@ typedef struct pg_be_sasl_mech
/* Common implementation for auth.c */
extern int CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port,
- char *shadow_pass, const char **logdetail);
+ const char **passwords, int num, const char **logdetail);
#endif /* PG_SASL_H */
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index c51e848c24d..70b01b511f0 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -21,7 +21,7 @@
extern PGDLLIMPORT const pg_be_sasl_mech pg_be_scram_mech;
/* Routines to handle and check SCRAM-SHA-256 secret */
-extern char *pg_be_scram_build_secret(const char *password);
+extern char *pg_be_scram_build_secret(const char *password, const char *salt);
extern bool parse_scram_secret(const char *secret, int *iterations, char **salt,
uint8 *stored_key, uint8 *server_key);
extern bool scram_verify_plain_password(const char *username,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 8a2ab405a28..bf84593a61c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -334,6 +334,7 @@ PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("passname", PASSNAME, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index c7bc648f46a..a543c181889 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,7 +43,7 @@ enum SysCacheIdentifier
AUTHMEMROLEMEM,
AUTHNAME,
AUTHOID,
- AUTHPASSWORD,
+ AUTHPASSWORDNAME,
CASTSOURCETARGET,
CLAAMNAMENSP,
CLAOID,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 4ffc41a5455..c6a84f86a84 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -120,7 +120,7 @@ CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
-- should not contain the original salt.
SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
- LEFT JOIN pg_auth_password p
+ LEFT JOIN pg_auth_password p
ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
--
2.35.1
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2022-06-29 21:21 Jacob Champion <[email protected]>
parent: Joshua Brindle <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Jacob Champion @ 2022-06-29 21:21 UTC (permalink / raw)
To: pgsql-hackers; +Cc: Stephen Frost <[email protected]>
On 4/8/22 10:04, Joshua Brindle wrote:
> It's unclear if I will be able to continue working on this featureset,
> this email address will be inactive after today.
I'm assuming the answer to this was "no". Is there any interest out
there to pick this up for the July CF?
--Jacob
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2022-06-29 21:27 Stephen Frost <[email protected]>
parent: Jacob Champion <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Stephen Frost @ 2022-06-29 21:27 UTC (permalink / raw)
To: Jacob Champion <[email protected]>; +Cc: pgsql-hackers
Greetings,
On Wed, Jun 29, 2022 at 17:22 Jacob Champion <[email protected]>
wrote:
> On 4/8/22 10:04, Joshua Brindle wrote:
> > It's unclear if I will be able to continue working on this featureset,
> > this email address will be inactive after today.
>
> I'm assuming the answer to this was "no". Is there any interest out
> there to pick this up for the July CF?
Short answer to that is yes, I’m interested in continuing this (though
certainly would welcome it if there are others who are also interested, and
may be able to bring someone else to help work on it too but that might be
more August / September time frame).
Thanks,
Stephen
>
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2022-06-30 23:53 Gurjeet Singh <[email protected]>
parent: Stephen Frost <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Gurjeet Singh @ 2022-06-30 23:53 UTC (permalink / raw)
To: Stephen Frost <[email protected]>; +Cc: Jacob Champion <[email protected]>; pgsql-hackers
I am planning on picking it up next week; right now picking up steam,
and reviewing a different, smaller patch.
At his behest, I had a conversation with Joshua (OP), and have his
support to pick up and continue working on this patch. I have a some
ideas of my own, on what this patch should do, but since I haven't
fully reviewed the (bulky) patch, I'll reserve my proposals until I
wrap my head around it.
Please expect some activity on this patch towards the end of next week.
BCC: Joshua's new work email.
Best regards,
Gurjeet
http://Gurje.et
On Wed, Jun 29, 2022 at 2:27 PM Stephen Frost <[email protected]> wrote:
>
> Greetings,
>
> On Wed, Jun 29, 2022 at 17:22 Jacob Champion <[email protected]> wrote:
>>
>> On 4/8/22 10:04, Joshua Brindle wrote:
>> > It's unclear if I will be able to continue working on this featureset,
>> > this email address will be inactive after today.
>>
>> I'm assuming the answer to this was "no". Is there any interest out
>> there to pick this up for the July CF?
>
>
> Short answer to that is yes, I’m interested in continuing this (though certainly would welcome it if there are others who are also interested, and may be able to bring someone else to help work on it too but that might be more August / September time frame).
>
> Thanks,
>
> Stephen
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2022-07-01 00:20 Stephen Frost <[email protected]>
parent: Gurjeet Singh <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Stephen Frost @ 2022-07-01 00:20 UTC (permalink / raw)
To: Gurjeet Singh <[email protected]>; +Cc: Jacob Champion <[email protected]>; pgsql-hackers
Greetings,
* Gurjeet Singh ([email protected]) wrote:
> I am planning on picking it up next week; right now picking up steam,
> and reviewing a different, smaller patch.
Great! Glad that others are interested in this.
> At his behest, I had a conversation with Joshua (OP), and have his
> support to pick up and continue working on this patch. I have a some
> ideas of my own, on what this patch should do, but since I haven't
> fully reviewed the (bulky) patch, I'll reserve my proposals until I
> wrap my head around it.
I'd be curious as to your thought as to what the patch should be doing.
Joshua and I had discussed it at some length as he was working on it.
> Please expect some activity on this patch towards the end of next week.
I've gone ahead and updated it, cleaned up a couple things, and make it
so that check-world actually passes with it. Attached is an updated
version and I'll add it to the July commitfest.
Thanks!
Stephen
Attachments:
[text/x-diff] multipass-v3.patch (175.3K, ../../[email protected]/2-multipass-v3.patch)
download | inline diff:
From 1203b8a92e4e6c51c7e5aa244d8ee9bbd2f74e66 Mon Sep 17 00:00:00 2001
From: Stephen Frost <[email protected]>
Date: Thu, 30 Jun 2022 18:15:52 -0400
Subject: [PATCH 1/3] Move rolpassword out of pg_authid into a new table
In preparation for more flexibility in password
management the rolpassword column needs to be
moved into a new table.
Author: Joshua Brindle
---
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/catalog.c | 8 +-
src/backend/catalog/system_views.sql | 5 +-
src/backend/commands/user.c | 226 ++++++++++++----
src/backend/libpq/auth-sasl.c | 2 +-
src/backend/libpq/auth-scram.c | 4 +-
src/backend/libpq/auth.c | 16 ++
src/backend/libpq/crypt.c | 40 ++-
src/backend/utils/cache/catcache.c | 1 +
src/backend/utils/cache/relcache.c | 13 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/initdb/initdb.c | 2 +-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/common/scram-common.c | 2 +-
src/include/catalog/pg_auth_password.h | 50 ++++
src/include/catalog/pg_authid.dat | 26 +-
src/include/catalog/pg_authid.h | 7 +-
src/include/commands/user.h | 1 +
src/include/libpq/crypt.h | 2 +-
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_index.out | 14 +-
src/test/regress/expected/oidjoins.out | 1 +
src/test/regress/expected/password.out | 44 ++--
src/test/regress/expected/roleattributes.out | 256 +++++++++----------
src/test/regress/expected/rules.out | 5 +-
src/test/regress/expected/tablespace.out | 12 +-
src/test/regress/sql/create_index.sql | 10 +-
src/test/regress/sql/password.sql | 32 ++-
src/test/regress/sql/roleattributes.sql | 64 ++---
src/test/regress/sql/tablespace.sql | 8 +-
30 files changed, 563 insertions(+), 316 deletions(-)
create mode 100644 src/include/catalog/pg_auth_password.h
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 89a0221ec9..450cfd9a58 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -63,7 +63,7 @@ CATALOG_HEADERS := \
pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
pg_database.h pg_db_role_setting.h pg_tablespace.h \
- pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
+ pg_authid.h pg_auth_members.h pg_auth_password.h pg_shdepend.h pg_shdescription.h \
pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index e784538aae..6a32e44c39 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -28,6 +28,7 @@
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
@@ -246,6 +247,7 @@ IsSharedRelation(Oid relationId)
{
/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
if (relationId == AuthIdRelationId ||
+ relationId == AuthPasswordRelationId ||
relationId == AuthMemRelationId ||
relationId == DatabaseRelationId ||
relationId == DbRoleSettingRelationId ||
@@ -260,6 +262,8 @@ IsSharedRelation(Oid relationId)
/* These are their indexes */
if (relationId == AuthIdOidIndexId ||
relationId == AuthIdRolnameIndexId ||
+ relationId == AuthPasswordRoleOidIndexId ||
+ relationId == AuthMemRoleMemIndexId ||
relationId == AuthMemMemRoleIndexId ||
relationId == AuthMemRoleMemIndexId ||
relationId == DatabaseNameIndexId ||
@@ -279,8 +283,8 @@ IsSharedRelation(Oid relationId)
relationId == TablespaceOidIndexId)
return true;
/* These are their toast tables and toast indexes */
- if (relationId == PgAuthidToastTable ||
- relationId == PgAuthidToastIndex ||
+ if (relationId == PgAuthPasswordToastTable ||
+ relationId == PgAuthPasswordToastIndex ||
relationId == PgDatabaseToastTable ||
relationId == PgDatabaseToastIndex ||
relationId == PgDbRoleSettingToastTable ||
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index fedaed533b..6989594022 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -40,11 +40,14 @@ CREATE VIEW pg_shadow AS
rolsuper AS usesuper,
rolreplication AS userepl,
rolbypassrls AS usebypassrls,
- rolpassword AS passwd,
+ p.password AS passwd,
rolvaliduntil AS valuntil,
setconfig AS useconfig
FROM pg_authid LEFT JOIN pg_db_role_setting s
ON (pg_authid.oid = setrole AND setdatabase = 0)
+ LEFT JOIN pg_auth_password p
+ ON p.roleid = pg_authid.oid
+
WHERE rolcanlogin;
REVOKE ALL ON pg_shadow FROM public;
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 984305ba31..b224b4a2e9 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -23,6 +23,7 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
#include "commands/comment.h"
@@ -64,6 +65,42 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+/*
+ * Is the role able to log in
+*/
+bool
+is_role_valid(const char *rolename, char **logdetail)
+{
+ HeapTuple roleTup;
+ Datum datum;
+ bool isnull;
+ TimestampTz vuntil = 0;
+
+ /* Get role info from pg_authid */
+ roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename));
+ if (!HeapTupleIsValid(roleTup))
+ {
+ *logdetail = psprintf(_("Role \"%s\" does not exist."),
+ rolename);
+ return false; /* no such user */
+ }
+
+ datum = SysCacheGetAttr(AUTHNAME, roleTup,
+ Anum_pg_authid_rolvaliduntil, &isnull);
+ ReleaseSysCache(roleTup);
+
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ if (!isnull && vuntil < GetCurrentTimestamp())
+ {
+ *logdetail = psprintf(_("User \"%s\" has an expired password."), rolename);
+ return false;
+ }
+
+ return true;
+}
+
/*
* CREATE ROLE
@@ -351,43 +388,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
-
- if (password)
- {
- char *shadow_pass;
- const char *logdetail = NULL;
-
- /*
- * Don't allow an empty password. Libpq treats an empty password the
- * same as no password at all, and won't even try to authenticate. But
- * other clients might, so allowing it would be confusing. By clearing
- * the password when an empty string is specified, the account is
- * consistently locked for all clients.
- *
- * Note that this only covers passwords stored in the database itself.
- * There are also checks in the authentication code, to forbid an
- * empty password from being used with authentication methods that
- * fetch the password from an external system, like LDAP or PAM.
- */
- if (password[0] == '\0' ||
- plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
- {
- ereport(NOTICE,
- (errmsg("empty string is not a valid password, clearing password")));
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
- }
- else
- {
- /* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, stmt->role,
- password);
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(shadow_pass);
- }
- }
- else
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -422,6 +422,60 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
*/
CatalogTupleInsert(pg_authid_rel, tuple);
+ if (password)
+ {
+ char *shadow_pass;
+ char *logdetail;
+ Datum new_password_record[Natts_pg_auth_password];
+ bool new_password_record_nulls[Natts_pg_auth_password];
+ Relation pg_auth_password_rel;
+ TupleDesc pg_auth_password_dsc;
+ HeapTuple new_tuple;
+
+ /*
+ * Don't allow an empty password. Libpq treats an empty password the
+ * same as no password at all, and won't even try to authenticate. But
+ * other clients might, so allowing it would be confusing. By clearing
+ * the password when an empty string is specified, the account is
+ * consistently locked for all clients.
+ *
+ * Note that this only covers passwords stored in the database itself.
+ * There are also checks in the authentication code, to forbid an
+ * empty password from being used with authentication methods that
+ * fetch the password from an external system, like LDAP or PAM.
+ */
+ if (password[0] == '\0' ||
+ plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
+ {
+ ereport(NOTICE,
+ (errmsg("empty string is not a valid password, clearing password")));
+ }
+ else
+ {
+ /* Encrypt the password to the requested format. */
+ shadow_pass = encrypt_password(Password_encryption, stmt->role,
+ password);
+
+ MemSet(new_password_record, 0, sizeof(new_password_record));
+ MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
+
+ /* open password table and insert it. */
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
+
+ new_password_record[Anum_pg_auth_password_password - 1] =
+ CStringGetTextDatum(shadow_pass);
+ new_password_record[Anum_pg_auth_password_roleid - 1] = roleid;
+
+ new_tuple = heap_form_tuple(pg_auth_password_dsc,
+ new_password_record, new_password_record_nulls);
+ CatalogTupleInsert(pg_auth_password_rel, new_tuple);
+ heap_freetuple(new_tuple);
+ table_close(pg_auth_password_rel, NoLock);
+
+ }
+ }
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -495,6 +549,9 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
Datum new_record[Natts_pg_authid];
bool new_record_nulls[Natts_pg_authid];
bool new_record_repl[Natts_pg_authid];
+ Datum new_password_record[Natts_pg_auth_password];
+ bool new_password_record_nulls[Natts_pg_auth_password];
+ bool new_password_record_repl[Natts_pg_auth_password];
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
HeapTuple tuple,
@@ -624,6 +681,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
rolename = pstrdup(NameStr(authform->rolname));
roleid = authform->oid;
+
/*
* To mess with a superuser or replication role in any way you gotta be
* superuser. We also insist on superuser to change the BYPASSRLS
@@ -694,6 +752,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
+ MemSet(new_password_record, 0, sizeof(new_password_record));
+ MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
+ MemSet(new_password_record_repl, false, sizeof(new_password_record_repl));
+
/*
* issuper/createrole/etc
@@ -752,24 +814,24 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
ereport(NOTICE,
(errmsg("empty string is not a valid password, clearing password")));
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
}
else
{
/* Encrypt the password to the requested format. */
shadow_pass = encrypt_password(Password_encryption, rolename,
password);
- new_record[Anum_pg_authid_rolpassword - 1] =
+ new_password_record[Anum_pg_auth_password_password - 1] =
CStringGetTextDatum(shadow_pass);
}
- new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
}
/* unset password */
if (dpassword && dpassword->arg == NULL)
{
- new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
}
/* valid until */
@@ -786,12 +848,48 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
new_record_nulls, new_record_repl);
CatalogTupleUpdate(pg_authid_rel, &tuple->t_self, new_tuple);
-
- InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
-
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
+ if (new_password_record_repl[Anum_pg_auth_password_password - 1] == true)
+ {
+ Relation pg_auth_password_rel;
+ TupleDesc pg_auth_password_dsc;
+ HeapTuple password_tuple;
+
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
+ password_tuple = SearchSysCache1(AUTHPASSWORD, ObjectIdGetDatum(roleid));
+
+ if (new_password_record_nulls[Anum_pg_auth_password_password - 1] == true) /* delete existing password */
+ {
+ if (HeapTupleIsValid(password_tuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &password_tuple->t_self);
+ ReleaseSysCache(password_tuple);
+ }
+ }
+ else if (HeapTupleIsValid(password_tuple)) /* update existing password */
+ {
+ new_tuple = heap_modify_tuple(password_tuple, pg_auth_password_dsc, new_password_record,
+ new_password_record_nulls, new_password_record_repl);
+ CatalogTupleUpdate(pg_auth_password_rel, &password_tuple->t_self, new_tuple);
+ ReleaseSysCache(password_tuple);
+ heap_freetuple(new_tuple);
+ }
+ else /* insert new password */
+ {
+ new_password_record[Anum_pg_auth_password_roleid - 1] = roleid;
+
+ new_tuple = heap_form_tuple(pg_auth_password_dsc,
+ new_password_record, new_password_record_nulls);
+ CatalogTupleInsert(pg_auth_password_rel, new_tuple);
+ heap_freetuple(new_tuple);
+ }
+
+ table_close(pg_auth_password_rel, NoLock);
+ }
+ InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -901,7 +999,6 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
return roleid;
}
-
/*
* DROP ROLE
*/
@@ -909,7 +1006,8 @@ void
DropRole(DropRoleStmt *stmt)
{
Relation pg_authid_rel,
- pg_auth_members_rel;
+ pg_auth_members_rel,
+ pg_auth_password_rel;
ListCell *item;
if (!have_createrole_privilege())
@@ -923,6 +1021,8 @@ DropRole(DropRoleStmt *stmt)
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_auth_members_rel = table_open(AuthMemRelationId, RowExclusiveLock);
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+
foreach(item, stmt->roles)
{
@@ -1056,6 +1156,15 @@ DropRole(DropRoleStmt *stmt)
DeleteSharedComments(roleid, AuthIdRelationId);
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
+ /*
+ * Drop password
+ */
+ tuple = SearchSysCache1(AUTHPASSWORD, roleid);
+ if (HeapTupleIsValid(tuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &tuple->t_self);
+ ReleaseSysCache(tuple);
+ }
+
/*
* Remove settings for this role.
*/
@@ -1077,7 +1186,9 @@ DropRole(DropRoleStmt *stmt)
* Now we can clean up; but keep locks until commit.
*/
table_close(pg_auth_members_rel, NoLock);
+ table_close(pg_auth_password_rel, NoLock);
table_close(pg_authid_rel, NoLock);
+
}
/*
@@ -1087,11 +1198,12 @@ ObjectAddress
RenameRole(const char *oldname, const char *newname)
{
HeapTuple oldtuple,
- newtuple;
+ newtuple,
+ passtuple;
TupleDesc dsc;
Relation rel;
Datum datum;
- bool isnull;
+ bool isnull = true;
Datum repl_val[Natts_pg_authid];
bool repl_null[Natts_pg_authid];
bool repl_repl[Natts_pg_authid];
@@ -1189,14 +1301,24 @@ RenameRole(const char *oldname, const char *newname)
CStringGetDatum(newname));
repl_null[Anum_pg_authid_rolname - 1] = false;
- datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
+ passtuple = SearchSysCache1(AUTHPASSWORD, roleid);
+
+ if (HeapTupleIsValid(passtuple))
+ datum = SysCacheGetAttr(AUTHPASSWORD, passtuple,
+ Anum_pg_auth_password_password, &isnull);
if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
{
+ Relation pg_auth_password_rel;
+
/* MD5 uses the username as salt, so just clear it on a rename */
- repl_repl[Anum_pg_authid_rolpassword - 1] = true;
- repl_null[Anum_pg_authid_rolpassword - 1] = true;
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ if (HeapTupleIsValid(passtuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
+ ReleaseSysCache(passtuple);
+ }
+ table_close(pg_auth_password_rel, NoLock);
ereport(NOTICE,
(errmsg("MD5 password cleared because of role rename")));
}
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index a1d7dbb6d5..805b3695b7 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -33,7 +33,7 @@
* implementation.
*
* shadow_pass is an optional pointer to the stored secret of the role
- * authenticated, from pg_authid.rolpassword. For mechanisms that use
+ * authenticated, from pg_auth_password.password. For mechanisms that use
* shadowed passwords, a NULL pointer here means that an entry could not
* be found for the role (or the user does not exist), and the mechanism
* should fail the authentication exchange.
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index ee7f52218a..795f1cba55 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -220,7 +220,7 @@ scram_get_mechanisms(Port *port, StringInfo buf)
* It should be one of the mechanisms that we support, as returned by
* scram_get_mechanisms().
*
- * 'shadow_pass' is the role's stored secret, from pg_authid.rolpassword.
+ * 'shadow_pass' is the role's stored secret, from pg_auth_password.password.
* The username was provided by the client in the startup message, and is
* available in port->user_name. If 'shadow_pass' is NULL, we still perform
* an authentication exchange, but it will fail, as if an incorrect password
@@ -454,7 +454,7 @@ scram_exchange(void *opaq, const char *input, int inputlen,
}
/*
- * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
+ * Construct a SCRAM secret, for storing in pg_auth_password.password.
*
* The result is palloc'd, so caller is responsible for freeing it.
*/
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index efc53f3135..269cf286ad 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -592,10 +592,26 @@ ClientAuthentication(Port *port)
case uaMD5:
case uaSCRAM:
+ /*
+ * check to be sure we are not past rolvaliduntil
+ */
+ if (!is_role_valid(port->user_name, &logdetail)) {
+ status = STATUS_ERROR;
+ break;
+ }
+
status = CheckPWChallengeAuth(port, &logdetail);
break;
case uaPassword:
+ /*
+ * check to be sure we are not past rolvaliduntil
+ */
+ if (!is_role_valid(port->user_name, &logdetail)) {
+ status = STATUS_ERROR;
+ break;
+ }
+
status = CheckPasswordAuth(port, &logdetail);
break;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 1ff8b0507d..745e61034c 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -2,7 +2,7 @@
*
* crypt.c
* Functions for dealing with encrypted passwords stored in
- * pg_authid.rolpassword.
+ * pg_auth_password.password.
*
* Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -16,6 +16,7 @@
#include <unistd.h>
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "common/md5.h"
#include "common/scram-common.h"
#include "libpq/crypt.h"
@@ -36,8 +37,7 @@
char *
get_role_password(const char *role, const char **logdetail)
{
- TimestampTz vuntil = 0;
- HeapTuple roleTup;
+ HeapTuple roleTup, passTup;
Datum datum;
bool isnull;
char *shadow_pass;
@@ -51,33 +51,29 @@ get_role_password(const char *role, const char **logdetail)
return NULL; /* no such user */
}
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolpassword, &isnull);
- if (isnull)
+ datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_oid, &isnull);
+
+ passTup = SearchSysCache1(AUTHPASSWORD, datum);
+
+ if (!HeapTupleIsValid(passTup))
{
- ReleaseSysCache(roleTup);
*logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
return NULL; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
-
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolvaliduntil, &isnull);
- if (!isnull)
- vuntil = DatumGetTimestampTz(datum);
+ datum = SysCacheGetAttr(AUTHPASSWORD, passTup,
+ Anum_pg_auth_password_password, &isnull);
ReleaseSysCache(roleTup);
-
- /*
- * Password OK, but check to be sure we are not past rolvaliduntil
- */
- if (!isnull && vuntil < GetCurrentTimestamp())
+ if (isnull) /* this should not happen any more but just in case */
{
- *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ ReleaseSysCache(passTup);
+ *logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
- return NULL;
+ return NULL; /* user has no password */
}
+ shadow_pass = TextDatumGetCString(datum);
+ ReleaseSysCache(passTup);
return shadow_pass;
}
@@ -156,7 +152,7 @@ encrypt_password(PasswordType target_type, const char *role,
* Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password or password hash, as stored
- * in pg_authid.rolpassword.
+ * in pg_auth_password.password.
* 'client_pass' is the response given by the remote user to the MD5 challenge.
* 'md5_salt' is the salt used in the MD5 authentication challenge.
*
@@ -211,7 +207,7 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
* Check given password for given user, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password hash, as stored in
- * pg_authid.rolpassword.
+ * pg_auth_password.password.
* 'client_pass' is the password given by the remote user.
*
* In the error case, store a string at *logdetail that will be sent to the
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 6ae7c1f50b..7c8f864a80 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1104,6 +1104,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
+ case AUTHPASSWORD:
case AUTHMEMMEMROLE:
case DATABASEOID:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index f502df91dc..b2ca82d1ab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_attrdef.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
@@ -113,6 +114,7 @@ static const FormData_pg_attribute Desc_pg_proc[Natts_pg_proc] = {Schema_pg_proc
static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type};
static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
+static const FormData_pg_attribute Desc_pg_auth_password[Natts_pg_auth_password] = {Schema_pg_auth_password};
static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel};
@@ -3485,6 +3487,7 @@ RelationBuildLocalRelation(const char *relname,
{
case DatabaseRelationId:
case AuthIdRelationId:
+ case AuthPasswordRelationId:
case AuthMemRelationId:
case RelationRelationId:
case AttributeRelationId:
@@ -3905,7 +3908,7 @@ RelationCacheInitialize(void)
* RelationCacheInitializePhase2
*
* This is called to prepare for access to shared catalogs during startup.
- * We must at least set up nailed reldescs for pg_database, pg_authid,
+ * We must at least set up nailed reldescs for pg_database, pg_authid, pg_auth_password,
* pg_auth_members, and pg_shseclabel. Ideally we'd like to have reldescs
* for their indexes, too. We attempt to load this information from the
* shared relcache init file. If that's missing or broken, just make
@@ -3950,8 +3953,10 @@ RelationCacheInitializePhase2(void)
Natts_pg_shseclabel, Desc_pg_shseclabel);
formrdesc("pg_subscription", SubscriptionRelation_Rowtype_Id, true,
Natts_pg_subscription, Desc_pg_subscription);
+ formrdesc("pg_auth_password", AuthPasswordRelation_Rowtype_Id, true,
+ Natts_pg_auth_password, Desc_pg_auth_password);
-#define NUM_CRITICAL_SHARED_RELS 5 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS 6 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
@@ -4090,8 +4095,10 @@ RelationCacheInitializePhase3(void)
AuthMemRelationId);
load_critical_index(SharedSecLabelObjectIndexId,
SharedSecLabelRelationId);
+ load_critical_index(AuthPasswordRoleOidIndexId,
+ AuthPasswordRelationId);
-#define NUM_CRITICAL_SHARED_INDEXES 6 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 7 /* fix if you change list above */
criticalSharedRelcachesBuilt = true;
}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 1912b12146..de2b3ec1eb 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -27,6 +27,7 @@
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
@@ -255,6 +256,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {AuthPasswordRelationId, /* AUTHPASSWORD */
+ AuthPasswordRoleOidIndexId,
+ 1,
+ {
+ Anum_pg_auth_password_roleid,
+ 0,
+ 0,
+ 0
+ },
+ 8
+ },
{
CastRelationId, /* CASTSOURCETARGET */
CastSourceTargetIndexId,
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index ed6de7ca94..7a39980e05 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1402,7 +1402,7 @@ setup_auth(FILE *cmdfd)
* The authid table shouldn't be readable except through views, to
* ensure passwords are not publicly visible.
*/
- "REVOKE ALL ON pg_authid FROM public;\n\n",
+ "REVOKE ALL ON pg_auth_password FROM public;\n\n",
NULL
};
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index ae41a652d7..64f27aa3f2 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -737,7 +737,18 @@ dumpRoles(PGconn *conn)
int i;
/* note: rolconfig is dumped later */
- if (server_version >= 90600)
+ if (server_version >= 150000)
+ printfPQExpBuffer(buf,
+ "SELECT oid, rolname, rolsuper, rolinherit, "
+ "rolcreaterole, rolcreatedb, "
+ "rolcanlogin, rolconnlimit, p.password as rolpassword, "
+ "rolvaliduntil, rolreplication, rolbypassrls, "
+ "pg_catalog.shobj_description(oid, '%s') as rolcomment, "
+ "rolname = current_user AS is_current_user "
+ "FROM %s LEFT JOIN pg_auth_password p ON %s.oid = p.roleid "
+ "WHERE rolname !~ '^pg_' "
+ "ORDER BY 2", role_catalog, role_catalog, role_catalog);
+ else if (server_version >= 90600)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 1268625929..d9d0cba737 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -181,7 +181,7 @@ scram_ServerKey(const uint8 *salted_password, uint8 *result,
/*
- * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
+ * Construct a SCRAM secret, for storing in pg_auth_password.password.
*
* The password should already have been processed with SASLprep, if necessary!
*
diff --git a/src/include/catalog/pg_auth_password.h b/src/include/catalog/pg_auth_password.h
new file mode 100644
index 0000000000..beaa2d40b9
--- /dev/null
+++ b/src/include/catalog/pg_auth_password.h
@@ -0,0 +1,50 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_password.h
+ * definition of the "authorization identifier" system catalog (pg_auth_password)
+ *
+ * Portions Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/pg_auth_password.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_PASSWORD_H
+#define PG_AUTH_PASSWORD_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_auth_password_d.h"
+
+/* ----------------
+ * pg_auth_password definition. cpp turns this into
+ * typedef struct FormData_pg_auth_password
+ * ----------------
+ */
+CATALOG(pg_auth_password,4548,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4549,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
+{
+ Oid roleid BKI_LOOKUP(pg_authid); /* ID of a role */
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text password; /* password */
+#endif
+} FormData_pg_auth_password;
+
+/* ----------------
+ * Form_pg_auth_password corresponds to a pointer to a tuple with
+ * the format of pg_auth_password relation.
+ * ----------------
+ */
+
+DECLARE_TOAST(pg_auth_password, 4175, 4176);
+#define PgAuthPasswordToastTable 4175
+#define PgAuthPasswordToastIndex 4176
+
+typedef FormData_pg_auth_password *Form_pg_auth_password;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4550, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops));
+
+
+#endif /* PG_AUTH_PASSWORD_H */
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 6c28119fa1..da36375a58 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -23,66 +23,66 @@
rolname => 'POSTGRES', rolsuper => 't', rolinherit => 't',
rolcreaterole => 't', rolcreatedb => 't', rolcanlogin => 't',
rolreplication => 't', rolbypassrls => 't', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6171', oid_symbol => 'ROLE_PG_DATABASE_OWNER',
rolname => 'pg_database_owner', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6181', oid_symbol => 'ROLE_PG_READ_ALL_DATA',
rolname => 'pg_read_all_data', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6182', oid_symbol => 'ROLE_PG_WRITE_ALL_DATA',
rolname => 'pg_write_all_data', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3373', oid_symbol => 'ROLE_PG_MONITOR',
rolname => 'pg_monitor', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3374', oid_symbol => 'ROLE_PG_READ_ALL_SETTINGS',
rolname => 'pg_read_all_settings', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3375', oid_symbol => 'ROLE_PG_READ_ALL_STATS',
rolname => 'pg_read_all_stats', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3377', oid_symbol => 'ROLE_PG_STAT_SCAN_TABLES',
rolname => 'pg_stat_scan_tables', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4569', oid_symbol => 'ROLE_PG_READ_SERVER_FILES',
rolname => 'pg_read_server_files', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4570', oid_symbol => 'ROLE_PG_WRITE_SERVER_FILES',
rolname => 'pg_write_server_files', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4571', oid_symbol => 'ROLE_PG_EXECUTE_SERVER_PROGRAM',
rolname => 'pg_execute_server_program', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4200', oid_symbol => 'ROLE_PG_SIGNAL_BACKEND',
rolname => 'pg_signal_backend', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4544', oid_symbol => 'ROLE_PG_CHECKPOINTER',
rolname => 'pg_checkpointer', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
]
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index 3512601c80..0cb5675a33 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -42,9 +42,8 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
int32 rolconnlimit; /* max connections allowed (-1=no limit) */
/* remaining fields may be null; use heap_getattr to read them! */
-#ifdef CATALOG_VARLEN /* variable-length fields start here */
- text rolpassword; /* password, if any */
- timestamptz rolvaliduntil; /* password expiration time, if any */
+#ifdef CATALOG_VARLEN
+ timestamptz rolvaliduntil BKI_FORCE_NULL; /* role expiration time, if any */
#endif
} FormData_pg_authid;
@@ -55,8 +54,6 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
*/
typedef FormData_pg_authid *Form_pg_authid;
-DECLARE_TOAST_WITH_MACRO(pg_authid, 4175, 4176, PgAuthidToastTable, PgAuthidToastIndex);
-
DECLARE_UNIQUE_INDEX(pg_authid_rolname_index, 2676, AuthIdRolnameIndexId, on pg_authid using btree(rolname name_ops));
DECLARE_UNIQUE_INDEX_PKEY(pg_authid_oid_index, 2677, AuthIdOidIndexId, on pg_authid using btree(oid oid_ops));
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d3dd8303d2..3e1aae5785 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -24,6 +24,7 @@ typedef void (*check_password_hook_type) (const char *username, const char *shad
extern PGDLLIMPORT check_password_hook_type check_password_hook;
+extern bool is_role_valid(const char *rolename, char **logdetail);
extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 3238cf66d3..b8ff8ccb41 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -21,7 +21,7 @@
* Plaintext passwords can be passed in by the user, in a CREATE/ALTER USER
* command. They will be encrypted to MD5 or SCRAM-SHA-256 format, before
* storing on-disk, so only MD5 and SCRAM-SHA-256 passwords should appear
- * in pg_authid.rolpassword. They are also the allowed values for the
+ * in pg_auth_password.password. They are also the allowed values for the
* password_encryption GUC.
*/
typedef enum PasswordType
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 4463ea66be..c7bc648f46 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,6 +43,7 @@ enum SysCacheIdentifier
AUTHMEMROLEMEM,
AUTHNAME,
AUTHOID,
+ AUTHPASSWORD,
CASTSOURCETARGET,
CLAAMNAMENSP,
CLAOID,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index d55aec3a1d..9875b277f2 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2515,10 +2515,10 @@ REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
ERROR: cannot reindex system catalogs concurrently
REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
ERROR: cannot reindex system catalogs concurrently
--- These are the toast table and index of pg_authid.
-REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
+-- These are the toast table and index of pg_database.
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1262; -- no catalog toast table
ERROR: cannot reindex system catalogs concurrently
-REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -- no catalog toast index
ERROR: cannot reindex system catalogs concurrently
REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
ERROR: cannot reindex system catalogs concurrently
@@ -2817,10 +2817,10 @@ ERROR: must be owner of schema schema_to_reindex
RESET ROLE;
GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
SET SESSION ROLE regress_reindexuser;
-REINDEX TABLE pg_toast.pg_toast_1260;
-ERROR: must be owner of table pg_toast_1260
-REINDEX INDEX pg_toast.pg_toast_1260_index;
-ERROR: must be owner of index pg_toast_1260_index
+REINDEX TABLE pg_toast.pg_toast_1262;
+ERROR: must be owner of table pg_toast_1262
+REINDEX INDEX pg_toast.pg_toast_1262_index;
+ERROR: must be owner of index pg_toast_1262_index
-- Clean up
RESET ROLE;
REVOKE USAGE ON SCHEMA pg_toast FROM regress_reindexuser;
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be..b69a2a72a7 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -197,6 +197,7 @@ NOTICE: checking pg_tablespace {spcowner} => pg_authid {oid}
NOTICE: checking pg_auth_members {roleid} => pg_authid {oid}
NOTICE: checking pg_auth_members {member} => pg_authid {oid}
NOTICE: checking pg_auth_members {grantor} => pg_authid {oid}
+NOTICE: checking pg_auth_password {roleid} => pg_authid {oid}
NOTICE: checking pg_shdepend {dbid} => pg_database {oid}
NOTICE: checking pg_shdepend {classid} => pg_class {oid}
NOTICE: checking pg_shdepend {refclassid} => pg_class {oid}
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 7c84c9da33..4ffc41a545 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -24,10 +24,12 @@ CREATE ROLE regress_passwd4 PASSWORD NULL;
--
-- Since the salt is random, the exact value stored will be different on every test
-- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
rolname | rolpassword_masked
-----------------+---------------------------------------------------
regress_passwd1 | md5783277baca28003b33453252be4dbb34
@@ -40,12 +42,14 @@ SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+
ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new;
NOTICE: MD5 password cleared because of role rename
-- md5 entry should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd2_new'
- ORDER BY rolname, rolpassword;
- rolname | rolpassword
----------------------+-------------
+ ORDER BY rolname, password;
+ rolname | password
+---------------------+----------
regress_passwd2_new |
(1 row)
@@ -72,10 +76,12 @@ CREATE ROLE regress_passwd6 PASSWORD 'SCRAM-SHA-256$1234';
CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
-- invalid length
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
rolname | rolpassword_masked
-----------------+---------------------------------------------------
regress_passwd1 | md5cd3578025fe2c3d7ed1b9a9b26238b70
@@ -95,9 +101,11 @@ ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
NOTICE: empty string is not a valid password, clearing password
ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
NOTICE: empty string is not a valid password, clearing password
-SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
- rolpassword
--------------
+SELECT password FROM pg_authid
+LEFT JOIN pg_auth_password p
+ON pg_authid.oid = p.roleid WHERE rolname='regress_passwd_empty';
+ password
+----------
(1 row)
@@ -110,8 +118,10 @@ CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
-- Check that the invalid secrets were re-hashed. A re-hashed secret
-- should not contain the original salt.
-SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
+SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
rolname | is_rolpassword_rehashed
@@ -134,11 +144,13 @@ DROP ROLE regress_passwd_sha_len0;
DROP ROLE regress_passwd_sha_len1;
DROP ROLE regress_passwd_sha_len2;
-- all entries should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
- rolname | rolpassword
----------+-------------
+ ORDER BY rolname, password;
+ rolname | password
+---------+----------
(0 rows)
diff --git a/src/test/regress/expected/roleattributes.out b/src/test/regress/expected/roleattributes.out
index 5e6969b173..18a9dacadd 100644
--- a/src/test/regress/expected/roleattributes.out
+++ b/src/test/regress/expected/roleattributes.out
@@ -1,233 +1,233 @@
-- default for superuser is false
CREATE ROLE regress_test_def_superuser;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_superuser | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
-- default for inherit is true
CREATE ROLE regress_test_def_inherit;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_inherit | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_inherit WITH INHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
-- default for create role is false
CREATE ROLE regress_test_def_createrole;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_createrole | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
-- default for create database is false
CREATE ROLE regress_test_def_createdb;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_createdb | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createdb WITH NOCREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
-- default for can login is false for role
CREATE ROLE regress_test_def_role_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_role_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
-- default for can login is true for user
CREATE USER regress_test_def_user_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_user_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
CREATE USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER USER regress_test_user_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
-- default for replication is false
CREATE ROLE regress_test_def_replication;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_replication | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_replication | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | t | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
ALTER ROLE regress_test_replication WITH NOREPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | t | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
-- default for bypassrls is false
CREATE ROLE regress_test_def_bypassrls;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_bypassrls | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
-- clean up roles
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index fc3cde3226..231fec7fda 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1725,11 +1725,12 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
pg_authid.rolsuper AS usesuper,
pg_authid.rolreplication AS userepl,
pg_authid.rolbypassrls AS usebypassrls,
- pg_authid.rolpassword AS passwd,
+ p.password AS passwd,
pg_authid.rolvaliduntil AS valuntil,
s.setconfig AS useconfig
- FROM (pg_authid
+ FROM ((pg_authid
LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
+ LEFT JOIN pg_auth_password p ON ((p.roleid = pg_authid.oid)))
WHERE pg_authid.rolcanlogin;
pg_shmem_allocations| SELECT pg_get_shmem_allocations.name,
pg_get_shmem_allocations.off,
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c52cf1cfcf..2ca2cd181f 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -53,13 +53,13 @@ ERROR: cannot move system relation "pg_authid_rolname_index"
REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
ERROR: cannot reindex system catalogs concurrently
-- toast relations, fail
-REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
-ERROR: cannot move system relation "pg_toast_1260_index"
-REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1260_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index;
+ERROR: cannot move system relation "pg_toast_1262_index"
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1262_index;
ERROR: cannot reindex system catalogs concurrently
-REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
-ERROR: cannot move system relation "pg_toast_1260_index"
-REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262;
+ERROR: cannot move system relation "pg_toast_1262_index"
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1262;
ERROR: cannot reindex system catalogs concurrently
-- system catalog, fail
REINDEX (TABLESPACE pg_global) TABLE pg_authid;
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index d8fded3d93..8d649f2355 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -1068,9 +1068,9 @@ REINDEX TABLE CONCURRENTLY concur_reindex_tab;
COMMIT;
REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
--- These are the toast table and index of pg_authid.
-REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
-REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index
+-- These are the toast table and index of pg_database.
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1262; -- no catalog toast table
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -- no catalog toast index
REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
-- Warns about catalog relations
REINDEX SCHEMA CONCURRENTLY pg_catalog;
@@ -1243,8 +1243,8 @@ REINDEX SCHEMA schema_to_reindex;
RESET ROLE;
GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
SET SESSION ROLE regress_reindexuser;
-REINDEX TABLE pg_toast.pg_toast_1260;
-REINDEX INDEX pg_toast.pg_toast_1260_index;
+REINDEX TABLE pg_toast.pg_toast_1262;
+REINDEX INDEX pg_toast.pg_toast_1262_index;
-- Clean up
RESET ROLE;
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 98f49916e5..f41d308fbb 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -23,18 +23,22 @@ CREATE ROLE regress_passwd4 PASSWORD NULL;
--
-- Since the salt is random, the exact value stored will be different on every test
-- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
-- Rename a role
ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new;
-- md5 entry should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd2_new'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
ALTER ROLE regress_passwd2_new RENAME TO regress_passwd2;
-- Change passwords with ALTER USER. With plaintext or already-encrypted
@@ -63,16 +67,20 @@ CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
-- invalid length
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
-- An empty password is not allowed, in any form
CREATE ROLE regress_passwd_empty PASSWORD '';
ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
-SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
+SELECT password FROM pg_authid
+LEFT JOIN pg_auth_password p
+ON pg_authid.oid = p.roleid WHERE rolname='regress_passwd_empty';
-- Test with invalid stored and server keys.
--
@@ -84,8 +92,10 @@ CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
-- Check that the invalid secrets were re-hashed. A re-hashed secret
-- should not contain the original salt.
-SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
+SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
@@ -103,7 +113,9 @@ DROP ROLE regress_passwd_sha_len1;
DROP ROLE regress_passwd_sha_len2;
-- all entries should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
diff --git a/src/test/regress/sql/roleattributes.sql b/src/test/regress/sql/roleattributes.sql
index c961b2d730..09505c6a3b 100644
--- a/src/test/regress/sql/roleattributes.sql
+++ b/src/test/regress/sql/roleattributes.sql
@@ -1,83 +1,83 @@
-- default for superuser is false
CREATE ROLE regress_test_def_superuser;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
CREATE ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
ALTER ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
-- default for inherit is true
CREATE ROLE regress_test_def_inherit;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
CREATE ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
ALTER ROLE regress_test_inherit WITH INHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
ALTER ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
-- default for create role is false
CREATE ROLE regress_test_def_createrole;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
CREATE ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
ALTER ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
-- default for create database is false
CREATE ROLE regress_test_def_createdb;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
CREATE ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
ALTER ROLE regress_test_createdb WITH NOCREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
ALTER ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
-- default for can login is false for role
CREATE ROLE regress_test_def_role_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
CREATE ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
ALTER ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
-- default for can login is true for user
CREATE USER regress_test_def_user_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
CREATE USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
ALTER USER regress_test_user_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
ALTER USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
-- default for replication is false
CREATE ROLE regress_test_def_replication;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
CREATE ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
ALTER ROLE regress_test_replication WITH NOREPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
ALTER ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
-- default for bypassrls is false
CREATE ROLE regress_test_def_bypassrls;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
-- clean up roles
DROP ROLE regress_test_def_superuser;
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 21db433f2a..d92135173f 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -42,10 +42,10 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am;
REINDEX (TABLESPACE regress_tblspace) TABLE pg_authid;
REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
-- toast relations, fail
-REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
-REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1260_index;
-REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
-REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1262_index;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262;
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1262;
-- system catalog, fail
REINDEX (TABLESPACE pg_global) TABLE pg_authid;
REINDEX (TABLESPACE pg_global) TABLE CONCURRENTLY pg_authid;
--
2.34.1
From bfb15c69fa4367e92cabeef1a97eb6fb394b788f Mon Sep 17 00:00:00 2001
From: Stephen Frost <[email protected]>
Date: Thu, 30 Jun 2022 18:16:52 -0400
Subject: [PATCH 2/3] multiple passwords work with scram and md5
also renaming roles, dropping roles, switching between
password_encryption
Author: Joshua Brindle
---
src/backend/commands/user.c | 198 ++++++++++++++++++++-----
src/backend/libpq/auth-sasl.c | 4 +-
src/backend/libpq/auth-scram.c | 181 ++++++++++++----------
src/backend/libpq/auth.c | 103 ++++++-------
src/backend/libpq/crypt.c | 51 ++++---
src/backend/parser/gram.y | 15 +-
src/backend/utils/cache/catcache.c | 2 +-
src/backend/utils/cache/syscache.c | 6 +-
src/include/catalog/pg_auth_password.h | 10 +-
src/include/commands/user.h | 2 +-
src/include/libpq/crypt.h | 2 +-
src/include/libpq/sasl.h | 4 +-
src/include/libpq/scram.h | 2 +-
src/include/parser/kwlist.h | 1 +
src/include/utils/syscache.h | 2 +-
src/test/regress/expected/password.out | 12 +-
16 files changed, 385 insertions(+), 210 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index b224b4a2e9..94747d7c3c 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -31,11 +31,14 @@
#include "commands/defrem.h"
#include "commands/seclabel.h"
#include "commands/user.h"
+#include "common/scram-common.h"
#include "libpq/crypt.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+#include "utils/catcache.h"
#include "utils/fmgroids.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
@@ -47,6 +50,9 @@ Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
/* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
+/* default password name */
+const char* default_passname = "__def__";
+
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -69,7 +75,7 @@ have_createrole_privilege(void)
* Is the role able to log in
*/
bool
-is_role_valid(const char *rolename, char **logdetail)
+is_role_valid(const char *rolename, const char **logdetail)
{
HeapTuple roleTup;
Datum datum;
@@ -102,6 +108,64 @@ is_role_valid(const char *rolename, char **logdetail)
}
+static
+bool
+validate_and_get_salt(char *rolename, char **salt, const char **logdetail)
+{
+ char **current_secrets;
+ char *salt1, *salt2 = NULL;
+ int i, num;
+ PasswordType passtype;
+
+ if (Password_encryption == PASSWORD_TYPE_MD5)
+ {
+ *salt = rolename; /* md5 always uses role name, no need to look through the passwords */
+ return true;
+ }
+ else if (Password_encryption == PASSWORD_TYPE_PLAINTEXT)
+ {
+ *salt = NULL; /* Plaintext does not have a salt */
+ return true;
+ }
+
+ current_secrets = get_role_passwords(rolename, logdetail, &num);
+ if (num == 0)
+ {
+ *salt = NULL; /* No existing passwords, allow one to be generated */
+ return true;
+ }
+ for (i = 0; i < num; i++) {
+ passtype = get_password_type(current_secrets[i]);
+ if (passtype == PASSWORD_TYPE_MD5 || passtype == PASSWORD_TYPE_PLAINTEXT)
+ continue; /* md5 uses rolename as salt so it is always the same and plaintext has no salt */
+ else if (passtype == PASSWORD_TYPE_SCRAM_SHA_256) {
+ int iterations;
+ uint8 stored_key[SCRAM_KEY_LEN];
+ uint8 server_key[SCRAM_KEY_LEN];
+
+ parse_scram_secret(current_secrets[i], &iterations, &salt1,
+ stored_key, server_key);
+
+ if (salt2 != NULL) {
+ if (strcmp(salt1, salt2)) {
+ *logdetail = psprintf(_("inconsistent salts, clearing password"));
+ *salt = NULL;
+ return false;
+ }
+ }
+ else
+ salt2 = salt1;
+ }
+
+ }
+ for (i = 0; i < num; i++)
+ pfree(current_secrets[i]);
+ pfree(current_secrets);
+ if (salt2)
+ *salt = pstrdup(salt2);
+ return true;
+}
+
/*
* CREATE ROLE
*/
@@ -117,6 +181,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
+ char *passname = NULL; /* name of the password for managing multiple passwords */
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -144,6 +209,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ DefElem *dpassName = NULL;
+
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -246,6 +313,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
+ else if (strcmp(defel->defname, "passname") == 0)
+ {
+ if (dpassName)
+ errorConflictingDefElem(defel, pstate);
+ dpassName = defel;
+ }
+
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -283,6 +357,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
validUntil = strVal(dvalidUntil->arg);
if (dbypassRLS)
bypassrls = boolVal(dbypassRLS->arg);
+ if (dpassName)
+ passname = strVal(dpassName->arg);
/* Check some permissions first */
if (issuper)
@@ -424,8 +500,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (password)
{
- char *shadow_pass;
- char *logdetail;
+ char *shadow_pass, *salt;
+ const char *logdetail;
Datum new_password_record[Natts_pg_auth_password];
bool new_password_record_nulls[Natts_pg_auth_password];
Relation pg_auth_password_rel;
@@ -453,12 +529,18 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
else
{
/* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, stmt->role,
+ validate_and_get_salt(stmt->role, &salt, &logdetail);
+ shadow_pass = encrypt_password(Password_encryption, salt,
password);
MemSet(new_password_record, 0, sizeof(new_password_record));
MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
-
+ if (passname != NULL)
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(passname));
+ else
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(default_passname));
/* open password table and insert it. */
pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
@@ -560,6 +642,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ListCell *option;
char *rolename;
char *password = NULL; /* user password */
+ char *passname = NULL; /* password name for managing multiple passwords */
int connlimit = -1; /* maximum connections allowed */
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
@@ -575,6 +658,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ DefElem *dpassName = NULL;
Oid roleid;
check_rolespec_name(stmt->role,
@@ -652,6 +736,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
+ else if (strcmp(defel->defname, "passname") == 0)
+ {
+ if (dpassName)
+ errorConflictingDefElem(defel, pstate);
+ dpassName = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -669,6 +759,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
}
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
+ if (dpassName)
+ passname = strVal(dpassName->arg);
/*
* Scan the pg_authid relation to be certain the user exists.
@@ -805,8 +897,9 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
/* password */
if (password)
{
- char *shadow_pass;
- const char *logdetail = NULL;
+ char *salt = NULL;
+ const char *logdetail = NULL;
+ char *shadow_pass;
/* Like in CREATE USER, don't allow an empty password. */
if (password[0] == '\0' ||
@@ -818,13 +911,26 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
}
else
{
- /* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, rolename,
- password);
- new_password_record[Anum_pg_auth_password_password - 1] =
- CStringGetTextDatum(shadow_pass);
+ if (!validate_and_get_salt(rolename, &salt, &logdetail))
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
+ else
+ {
+ /* Encrypt the password to the requested format. */
+ shadow_pass = encrypt_password(Password_encryption, salt,
+ password);
+ new_password_record[Anum_pg_auth_password_password - 1] =
+ CStringGetTextDatum(shadow_pass);
+
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
+
+ if (passname)
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(passname));
+ else
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(default_passname));
+ }
}
- new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
}
/* unset password */
@@ -859,7 +965,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
- password_tuple = SearchSysCache1(AUTHPASSWORD, ObjectIdGetDatum(roleid));
+ if (dpassName)
+ password_tuple = SearchSysCache2(AUTHPASSWORDNAME, ObjectIdGetDatum(roleid), CStringGetDatum(passname));
+ else
+ password_tuple = SearchSysCache2(AUTHPASSWORDNAME, ObjectIdGetDatum(roleid), CStringGetDatum(default_passname));
if (new_password_record_nulls[Anum_pg_auth_password_password - 1] == true) /* delete existing password */
{
@@ -1157,14 +1266,23 @@ DropRole(DropRoleStmt *stmt)
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
/*
- * Drop password
+ * Drop password(s)
*/
- tuple = SearchSysCache1(AUTHPASSWORD, roleid);
- if (HeapTupleIsValid(tuple)) {
- CatalogTupleDelete(pg_auth_password_rel, &tuple->t_self);
- ReleaseSysCache(tuple);
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_password_roleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ sscan = systable_beginscan(pg_auth_password_rel, AuthPasswordRoleOidIndexId,
+ true, NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+ {
+ CatalogTupleDelete(pg_auth_password_rel, &tmp_tuple->t_self);
}
+ systable_endscan(sscan);
+
/*
* Remove settings for this role.
*/
@@ -1201,6 +1319,9 @@ RenameRole(const char *oldname, const char *newname)
newtuple,
passtuple;
TupleDesc dsc;
+ ScanKeyData scankey;
+ SysScanDesc sscan;
+
Relation rel;
Datum datum;
bool isnull = true;
@@ -1211,6 +1332,7 @@ RenameRole(const char *oldname, const char *newname)
Oid roleid;
ObjectAddress address;
Form_pg_authid authform;
+ Relation pg_auth_password_rel;
rel = table_open(AuthIdRelationId, RowExclusiveLock);
dsc = RelationGetDescr(rel);
@@ -1301,28 +1423,38 @@ RenameRole(const char *oldname, const char *newname)
CStringGetDatum(newname));
repl_null[Anum_pg_authid_rolname - 1] = false;
- passtuple = SearchSysCache1(AUTHPASSWORD, roleid);
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_password_roleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
- if (HeapTupleIsValid(passtuple))
- datum = SysCacheGetAttr(AUTHPASSWORD, passtuple,
- Anum_pg_auth_password_password, &isnull);
+ sscan = systable_beginscan(pg_auth_password_rel, AuthPasswordRoleOidIndexId,
+ true, NULL, 1, &scankey);
- if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
+ while (HeapTupleIsValid(passtuple = systable_getnext(sscan)))
{
- Relation pg_auth_password_rel;
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, passtuple,
+ Anum_pg_auth_password_password, &isnull);
- /* MD5 uses the username as salt, so just clear it on a rename */
- pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
+ {
- if (HeapTupleIsValid(passtuple)) {
- CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
- ReleaseSysCache(passtuple);
+ /* MD5 uses the username as salt, so just clear it on a rename */
+
+ if (HeapTupleIsValid(passtuple)) {
+ CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
+ ereport(NOTICE,
+ (errmsg("MD5 password cleared because of role rename")));
+
+ }
}
- table_close(pg_auth_password_rel, NoLock);
- ereport(NOTICE,
- (errmsg("MD5 password cleared because of role rename")));
}
+ systable_endscan(sscan);
+ table_close(pg_auth_password_rel, NoLock);
+
newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
CatalogTupleUpdate(rel, &oldtuple->t_self, newtuple);
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index 805b3695b7..652c18fa94 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -49,7 +49,7 @@
* should just pass NULL.
*/
int
-CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
+CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, const char **passwords, int num,
const char **logdetail)
{
StringInfoData sasl_mechs;
@@ -136,7 +136,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
* This is because we don't want to reveal to an attacker what
* usernames are valid, nor which users have a valid password.
*/
- opaq = mech->init(port, selected_mech, shadow_pass);
+ opaq = mech->init(port, selected_mech, passwords, num);
inputlen = pq_getmsgint(&buf, 4);
if (inputlen == -1)
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 795f1cba55..7dfc66b64b 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -109,7 +109,7 @@
static void scram_get_mechanisms(Port *port, StringInfo buf);
static void *scram_init(Port *port, const char *selected_mech,
- const char *shadow_pass);
+ const char **secrets, const int num_secrets);
static int scram_exchange(void *opaq, const char *input, int inputlen,
char **output, int *outputlen,
const char **logdetail);
@@ -132,6 +132,13 @@ typedef enum
SCRAM_AUTH_FINISHED
} scram_state_enum;
+typedef struct
+{
+ uint8 StoredKey[SCRAM_KEY_LEN];
+ uint8 ServerKey[SCRAM_KEY_LEN];
+} scram_secret;
+
+
typedef struct
{
scram_state_enum state;
@@ -141,10 +148,16 @@ typedef struct
Port *port;
bool channel_binding_in_use;
+ /*
+ * The salt and iterations must be the same for all
+ * secrets since they are sent as part of the initial message
+ */
int iterations;
char *salt; /* base64-encoded */
- uint8 StoredKey[SCRAM_KEY_LEN];
- uint8 ServerKey[SCRAM_KEY_LEN];
+ /* Array of possible secrets */
+ scram_secret *secrets;
+ int num_secrets;
+ int chosen_secret; /* secret chosen during final client message */
/* Fields of the first message from client */
char cbind_flag;
@@ -220,17 +233,20 @@ scram_get_mechanisms(Port *port, StringInfo buf)
* It should be one of the mechanisms that we support, as returned by
* scram_get_mechanisms().
*
- * 'shadow_pass' is the role's stored secret, from pg_auth_password.password.
+ * 'passwords' are the role's stored secrets, from pg_auth_password.password.
* The username was provided by the client in the startup message, and is
* available in port->user_name. If 'shadow_pass' is NULL, we still perform
* an authentication exchange, but it will fail, as if an incorrect password
* was given.
*/
static void *
-scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
+scram_init(Port *port, const char *selected_mech, const char **secrets, const int num_secrets)
{
scram_state *state;
- bool got_secret;
+ bool got_secret = false;
+ int i;
+ int iterations;
+ char *salt = NULL; /* base64-encoded */
state = (scram_state *) palloc0(sizeof(scram_state));
state->port = port;
@@ -260,46 +276,47 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
/*
* Parse the stored secret.
*/
- if (shadow_pass)
+ if (secrets)
{
- int password_type = get_password_type(shadow_pass);
-
- if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
+ state->secrets = palloc0(sizeof(scram_secret) * num_secrets);
+ state->num_secrets = num_secrets;
+ for (i = 0; i < num_secrets; i++)
{
- if (parse_scram_secret(shadow_pass, &state->iterations, &state->salt,
- state->StoredKey, state->ServerKey))
- got_secret = true;
- else
+ int password_type = get_password_type(secrets[i]);
+
+ if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
{
- /*
- * The password looked like a SCRAM secret, but could not be
- * parsed.
- */
- ereport(LOG,
- (errmsg("invalid SCRAM secret for user \"%s\"",
- state->port->user_name)));
- got_secret = false;
+ if (parse_scram_secret(secrets[i], &state->iterations, &state->salt,
+ state->secrets[i].StoredKey, state->secrets[i].ServerKey))
+ {
+ if (salt) {
+ /* The stored iterations and salt must match or we cannot proceed, allow failure via mock */
+ if (strcmp(salt, state->salt) || iterations != state->iterations) {
+ ereport(WARNING, (errmsg("inconsistent salt or iterations for user \"%s\"",
+ state->port->user_name)));
+ got_secret = false; /* fail and allow mock creditials to be created */
+ break;
+ }
+ }
+ else
+ {
+ salt = state->salt;
+ iterations = state->iterations;
+ got_secret = true; /* We got at least one good SCRAM secret */
+ }
+ }
+ else
+ {
+ /*
+ * The password looked like a SCRAM secret, but could not be
+ * parsed.
+ */
+ ereport(LOG,
+ (errmsg("invalid SCRAM secret for user \"%s\"",
+ state->port->user_name)));
+ }
}
}
- else
- {
- /*
- * The user doesn't have SCRAM secret. (You cannot do SCRAM
- * authentication with an MD5 hash.)
- */
- state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM secret."),
- state->port->user_name);
- got_secret = false;
- }
- }
- else
- {
- /*
- * The caller requested us to perform a dummy authentication. This is
- * considered normal, since the caller requested it, so don't set log
- * detail.
- */
- got_secret = false;
}
/*
@@ -310,8 +327,10 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
*/
if (!got_secret)
{
+ state->secrets = palloc0(sizeof(scram_secret));
+
mock_scram_secret(state->port->user_name, &state->iterations,
- &state->salt, state->StoredKey, state->ServerKey);
+ &state->salt, state->secrets[0].StoredKey, state->secrets[0].ServerKey);
state->doomed = true;
}
@@ -459,7 +478,7 @@ scram_exchange(void *opaq, const char *input, int inputlen,
* The result is palloc'd, so caller is responsible for freeing it.
*/
char *
-pg_be_scram_build_secret(const char *password)
+pg_be_scram_build_secret(const char *password, const char *salt)
{
char *prep_password;
pg_saslprep_rc rc;
@@ -476,11 +495,13 @@ pg_be_scram_build_secret(const char *password)
if (rc == SASLPREP_SUCCESS)
password = (const char *) prep_password;
- /* Generate random salt */
- if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
+ /* Use passed in salt or generate random salt */
+ if (!salt && !pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate random salt")));
+ else if (salt)
+ pg_b64_decode(salt, strlen(salt), saltbuf, SCRAM_DEFAULT_SALT_LEN);
result = scram_build_secret(saltbuf, SCRAM_DEFAULT_SALT_LEN,
SCRAM_DEFAULT_ITERATIONS, password,
@@ -1114,47 +1135,57 @@ verify_client_proof(scram_state *state)
uint8 ClientSignature[SCRAM_KEY_LEN];
uint8 ClientKey[SCRAM_KEY_LEN];
uint8 client_StoredKey[SCRAM_KEY_LEN];
- pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
- int i;
+ pg_hmac_ctx *ctx;
+ int i, j;
const char *errstr = NULL;
-
/*
* Calculate ClientSignature. Note that we don't log directly a failure
* here even when processing the calculations as this could involve a mock
* authentication.
*/
- if (pg_hmac_init(ctx, state->StoredKey, SCRAM_KEY_LEN) < 0 ||
- pg_hmac_update(ctx,
- (uint8 *) state->client_first_message_bare,
- strlen(state->client_first_message_bare)) < 0 ||
- pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
- pg_hmac_update(ctx,
- (uint8 *) state->server_first_message,
- strlen(state->server_first_message)) < 0 ||
- pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
- pg_hmac_update(ctx,
- (uint8 *) state->client_final_message_without_proof,
- strlen(state->client_final_message_without_proof)) < 0 ||
- pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+ for (j = 0; j < state->num_secrets; j++)
{
- elog(ERROR, "could not calculate client signature: %s",
- pg_hmac_error(ctx));
- }
+ ctx = pg_hmac_create(PG_SHA256);
+ elog(LOG, "Trying to verify password %d", j);
+
+ if (pg_hmac_init(ctx, state->secrets[j].StoredKey, SCRAM_KEY_LEN) < 0 ||
+ pg_hmac_update(ctx,
+ (uint8 *) state->client_first_message_bare,
+ strlen(state->client_first_message_bare)) < 0 ||
+ pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
+ pg_hmac_update(ctx,
+ (uint8 *) state->server_first_message,
+ strlen(state->server_first_message)) < 0 ||
+ pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
+ pg_hmac_update(ctx,
+ (uint8 *) state->client_final_message_without_proof,
+ strlen(state->client_final_message_without_proof)) < 0 ||
+ pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
+ {
+ elog(LOG, "could not calculate client signature");
+ pg_hmac_free(ctx);
+ continue;
+ }
- pg_hmac_free(ctx);
+ elog(LOG, "success on %d", j);
- /* Extract the ClientKey that the client calculated from the proof */
- for (i = 0; i < SCRAM_KEY_LEN; i++)
- ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+ pg_hmac_free(ctx);
- /* Hash it one more time, and compare with StoredKey */
- if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
- elog(ERROR, "could not hash stored key: %s", errstr);
+ for (i = 0; i < SCRAM_KEY_LEN; i++)
+ ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
- if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
- return false;
+ /* Hash it one more time, and compare with StoredKey */
+ if (scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey, &errstr) < 0)
+ elog(ERROR, "could not hash stored key: %s", errstr);
- return true;
+ if (memcmp(client_StoredKey, state->secrets[j].StoredKey, SCRAM_KEY_LEN) == 0) {
+ elog(LOG, "Moving forward with Password %d", j);
+ state->chosen_secret = j;
+ return true;
+ }
+ }
+
+ return false;
}
/*
@@ -1380,7 +1411,7 @@ build_server_final_message(scram_state *state)
pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
/* calculate ServerSignature */
- if (pg_hmac_init(ctx, state->ServerKey, SCRAM_KEY_LEN) < 0 ||
+ if (pg_hmac_init(ctx, state->secrets[state->chosen_secret].ServerKey, SCRAM_KEY_LEN) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 269cf286ad..4cc59d39ce 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -58,8 +58,7 @@ static void set_authn_id(Port *port, const char *id);
static int CheckPasswordAuth(Port *port, const char **logdetail);
static int CheckPWChallengeAuth(Port *port, const char **logdetail);
-static int CheckMD5Auth(Port *port, char *shadow_pass,
- const char **logdetail);
+static int CheckMD5Auth(Port *port, const char **passwords, int num, const char **logdetail);
/*----------------------------------------------------------------
@@ -790,8 +789,9 @@ static int
CheckPasswordAuth(Port *port, const char **logdetail)
{
char *passwd;
- int result;
- char *shadow_pass;
+ int result = STATUS_ERROR;
+ int i, num;
+ char **passwords;
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
@@ -799,17 +799,21 @@ CheckPasswordAuth(Port *port, const char **logdetail)
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
- shadow_pass = get_role_password(port->user_name, logdetail);
- if (shadow_pass)
- {
- result = plain_crypt_verify(port->user_name, shadow_pass, passwd,
- logdetail);
+ passwords = get_role_passwords(port->user_name, logdetail, &num);
+ if (passwords != NULL) {
+ for (i = 0; i < num; i++)
+ {
+ result = plain_crypt_verify(port->user_name, passwords[i], passwd,
+ logdetail);
+ if (result == STATUS_OK)
+ break; /* Found a matching password, no need to try any others */
+ }
+ for (i = 0; i < num; i++)
+ pfree(passwords[i]);
+
+ pfree(passwords);
}
- else
- result = STATUS_ERROR;
- if (shadow_pass)
- pfree(shadow_pass);
pfree(passwd);
if (result == STATUS_OK)
@@ -824,57 +828,42 @@ CheckPasswordAuth(Port *port, const char **logdetail)
static int
CheckPWChallengeAuth(Port *port, const char **logdetail)
{
- int auth_result;
- char *shadow_pass;
- PasswordType pwtype;
+ bool scram_pw_avail = false;
+ int auth_result = STATUS_ERROR;
+ int i, num;
+ char **passwords;
Assert(port->hba->auth_method == uaSCRAM ||
port->hba->auth_method == uaMD5);
- /* First look up the user's password. */
- shadow_pass = get_role_password(port->user_name, logdetail);
-
- /*
- * If the user does not exist, or has no password or it's expired, we
- * still go through the motions of authentication, to avoid revealing to
- * the client that the user didn't exist. If 'md5' is allowed, we choose
- * whether to use 'md5' or 'scram-sha-256' authentication based on current
- * password_encryption setting. The idea is that most genuine users
- * probably have a password of that type, and if we pretend that this user
- * had a password of that type, too, it "blends in" best.
- */
- if (!shadow_pass)
- pwtype = Password_encryption;
- else
- pwtype = get_password_type(shadow_pass);
+ /* First look up the user's passwords. */
+ passwords = get_role_passwords(port->user_name, logdetail, &num);
/*
* If 'md5' authentication is allowed, decide whether to perform 'md5' or
* 'scram-sha-256' authentication based on the type of password the user
- * has. If it's an MD5 hash, we must do MD5 authentication, and if it's a
- * SCRAM secret, we must do SCRAM authentication.
+ * has. If there's a SCRAM PW available then we'll do SCRAM, otherwise we
+ * will fall back to trying to use MD5.
*
* If MD5 authentication is not allowed, always use SCRAM. If the user
* had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will
* fail.
*/
- if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5)
- auth_result = CheckMD5Auth(port, shadow_pass, logdetail);
- else
- auth_result = CheckSASLAuth(&pg_be_scram_mech, port, shadow_pass,
- logdetail);
+ if (passwords != NULL) {
+ for (i = 0; i < num; i++)
+ if (get_password_type(passwords[i]) == PASSWORD_TYPE_SCRAM_SHA_256)
+ scram_pw_avail = true;
+
+ if (port->hba->auth_method == uaMD5 && !scram_pw_avail)
+ auth_result = CheckMD5Auth(port, (const char **) passwords, num, logdetail);
+ else
+ auth_result = CheckSASLAuth(&pg_be_scram_mech, port, (const char **) passwords, num,
+ logdetail);
- if (shadow_pass)
- pfree(shadow_pass);
+ for (i = 0; i < num; i++)
+ pfree(passwords[i]);
- /*
- * If get_role_password() returned error, return error, even if the
- * authentication succeeded.
- */
- if (!shadow_pass)
- {
- Assert(auth_result != STATUS_OK);
- return STATUS_ERROR;
+ pfree(passwords);
}
if (auth_result == STATUS_OK)
@@ -884,11 +873,12 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
}
static int
-CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
+CheckMD5Auth(Port *port, const char **passwords, int num, const char **logdetail)
{
char md5Salt[4]; /* Password salt */
char *passwd;
- int result;
+ int result = STATUS_ERROR;
+ int i;
if (Db_user_namespace)
ereport(FATAL,
@@ -909,12 +899,13 @@ CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
- if (shadow_pass)
- result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
+ for (i = 0; i < num; i++)
+ {
+ result = md5_crypt_verify(port->user_name, passwords[i], passwd,
md5Salt, 4, logdetail);
- else
- result = STATUS_ERROR;
-
+ if (result == STATUS_OK)
+ break;
+ }
pfree(passwd);
return result;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 745e61034c..09b6bebf40 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -23,6 +23,7 @@
#include "libpq/scram.h"
#include "miscadmin.h"
#include "utils/builtins.h"
+#include "utils/catcache.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
@@ -34,13 +35,15 @@
* for the postmaster log, in *logdetail. The error reason should *not* be
* sent to the client, to avoid giving away user information!
*/
-char *
-get_role_password(const char *role, const char **logdetail)
+char **
+get_role_passwords(const char *role, const char **logdetail, int *num)
{
- HeapTuple roleTup, passTup;
+ HeapTuple roleTup;
Datum datum;
bool isnull;
- char *shadow_pass;
+ char **passwords;
+ CatCList *passlist;
+ int i;
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
@@ -52,30 +55,34 @@ get_role_password(const char *role, const char **logdetail)
}
datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_oid, &isnull);
-
- passTup = SearchSysCache1(AUTHPASSWORD, datum);
+ ReleaseSysCache(roleTup);
+ /* Find any existing password that is not the one being updated to get the salt */
+ passlist = SearchSysCacheList1(AUTHPASSWORDNAME, datum);
+ *num = passlist->n_members;
- if (!HeapTupleIsValid(passTup))
+ if (passlist->n_members == 0)
{
+ *num = 0;
*logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
+ ReleaseCatCacheList(passlist);
return NULL; /* user has no password */
}
- datum = SysCacheGetAttr(AUTHPASSWORD, passTup,
- Anum_pg_auth_password_password, &isnull);
- ReleaseSysCache(roleTup);
- if (isnull) /* this should not happen any more but just in case */
+ passwords = palloc0(sizeof(char *) * passlist->n_members);
+
+ for (i = 0; i < passlist->n_members; i++)
{
- ReleaseSysCache(passTup);
- *logdetail = psprintf(_("User \"%s\" has no password assigned."),
- role);
- return NULL; /* user has no password */
+ HeapTuple tup = &passlist->members[i]->tuple;
+
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_password, &isnull);
+ passwords[i] = TextDatumGetCString(datum);
}
- shadow_pass = TextDatumGetCString(datum);
- ReleaseSysCache(passTup);
- return shadow_pass;
+ ReleaseCatCacheList(passlist);
+
+ return passwords;
}
/*
@@ -107,7 +114,7 @@ get_password_type(const char *shadow_pass)
* hash, so it is stored as it is regardless of the requested type.
*/
char *
-encrypt_password(PasswordType target_type, const char *role,
+encrypt_password(PasswordType target_type, const char *salt,
const char *password)
{
PasswordType guessed_type = get_password_type(password);
@@ -128,13 +135,13 @@ encrypt_password(PasswordType target_type, const char *role,
case PASSWORD_TYPE_MD5:
encrypted_password = palloc(MD5_PASSWD_LEN + 1);
- if (!pg_md5_encrypt(password, role, strlen(role),
+ if (!pg_md5_encrypt(password, salt, strlen(salt),
encrypted_password, &errstr))
- elog(ERROR, "password encryption failed: %s", errstr);
+ elog(ERROR, "password encryption failed %s", errstr);
return encrypted_password;
case PASSWORD_TYPE_SCRAM_SHA_256:
- return pg_be_scram_build_secret(password);
+ return pg_be_scram_build_secret(password, salt);
case PASSWORD_TYPE_PLAINTEXT:
elog(ERROR, "cannot encrypt password with 'plaintext'");
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 969c9c158f..7c1d023b7e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -826,8 +826,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
- PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PATH
- PLACING PLAN PLANS POLICY
+ PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSNAME PASSWORD
+ PATH PLACING PLAN PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
@@ -1261,6 +1261,15 @@ AlterOptRoleElem:
errhint("Remove UNENCRYPTED to store the password in encrypted form instead."),
parser_errposition(@1)));
}
+ | PASSNAME Sconst
+ {
+ $$ = makeDefElem("passname",
+ (Node *)makeString($2), @1);
+ }
+ | VALID FOR Sconst
+ {
+ $$ = makeDefElem("validFor", (Node *)makeString($3), @1);
+ }
| INHERIT
{
$$ = makeDefElem("inherit", (Node *) makeBoolean(true), @1);
@@ -17863,6 +17872,7 @@ unreserved_keyword:
| PARTIAL
| PARTITION
| PASSING
+ | PASSNAME
| PASSWORD
| PATH
| PLAN
@@ -18481,6 +18491,7 @@ bare_label_keyword:
| PARTIAL
| PARTITION
| PASSING
+ | PASSNAME
| PASSWORD
| PATH
| PLACING
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 7c8f864a80..e487cef5b3 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1104,7 +1104,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
- case AUTHPASSWORD:
+ case AUTHPASSWORDNAME:
case AUTHMEMMEMROLE:
case DATABASEOID:
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index de2b3ec1eb..9879120bf7 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -256,12 +256,12 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
- {AuthPasswordRelationId, /* AUTHPASSWORD */
+ {AuthPasswordRelationId, /* AUTHPASSWORDNAME */
AuthPasswordRoleOidIndexId,
- 1,
+ 2,
{
Anum_pg_auth_password_roleid,
- 0,
+ Anum_pg_auth_password_name,
0,
0
},
diff --git a/src/include/catalog/pg_auth_password.h b/src/include/catalog/pg_auth_password.h
index beaa2d40b9..2100cda758 100644
--- a/src/include/catalog/pg_auth_password.h
+++ b/src/include/catalog/pg_auth_password.h
@@ -24,11 +24,14 @@
* typedef struct FormData_pg_auth_password
* ----------------
*/
-CATALOG(pg_auth_password,4548,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4549,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
+CATALOG(pg_auth_password,4551,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4552,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
{
Oid roleid BKI_LOOKUP(pg_authid); /* ID of a role */
+ NameData name; /* name of password for multiple password support */
+
#ifdef CATALOG_VARLEN /* variable-length fields start here */
- text password; /* password */
+ text password BKI_FORCE_NOT_NULL; /* password */
+ timestamptz expiration BKI_FORCE_NULL; /* password expiration time, if any */
#endif
} FormData_pg_auth_password;
@@ -44,7 +47,6 @@ DECLARE_TOAST(pg_auth_password, 4175, 4176);
typedef FormData_pg_auth_password *Form_pg_auth_password;
-DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4550, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops));
-
+DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 4553, AuthPasswordRoleOidIndexId, on pg_auth_password using btree(roleid oid_ops, name name_ops));
#endif /* PG_AUTH_PASSWORD_H */
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 3e1aae5785..1c14f60a05 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -24,7 +24,7 @@ typedef void (*check_password_hook_type) (const char *username, const char *shad
extern PGDLLIMPORT check_password_hook_type check_password_hook;
-extern bool is_role_valid(const char *rolename, char **logdetail);
+extern bool is_role_valid(const char *rolename, const char **logdetail);
extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index b8ff8ccb41..84c9c4b4f4 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -35,7 +35,7 @@ extern PasswordType get_password_type(const char *shadow_pass);
extern char *encrypt_password(PasswordType target_type, const char *role,
const char *password);
-extern char *get_role_password(const char *role, const char **logdetail);
+extern char **get_role_passwords(const char *role, const char **logdetail, int *num);
extern int md5_crypt_verify(const char *role, const char *shadow_pass,
const char *client_pass, const char *md5_salt,
diff --git a/src/include/libpq/sasl.h b/src/include/libpq/sasl.h
index 39ccf8f0e3..27b4e57033 100644
--- a/src/include/libpq/sasl.h
+++ b/src/include/libpq/sasl.h
@@ -77,7 +77,7 @@ typedef struct pg_be_sasl_mech
* disclosing valid user names.
*---------
*/
- void *(*init) (Port *port, const char *mech, const char *shadow_pass);
+ void *(*init) (Port *port, const char *mech, const char **secrets, const int num_secrets);
/*---------
* exchange()
@@ -131,6 +131,6 @@ typedef struct pg_be_sasl_mech
/* Common implementation for auth.c */
extern int CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port,
- char *shadow_pass, const char **logdetail);
+ const char **passwords, int num, const char **logdetail);
#endif /* PG_SASL_H */
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index c51e848c24..70b01b511f 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -21,7 +21,7 @@
extern PGDLLIMPORT const pg_be_sasl_mech pg_be_scram_mech;
/* Routines to handle and check SCRAM-SHA-256 secret */
-extern char *pg_be_scram_build_secret(const char *password);
+extern char *pg_be_scram_build_secret(const char *password, const char *salt);
extern bool parse_scram_secret(const char *secret, int *iterations, char **salt,
uint8 *stored_key, uint8 *server_key);
extern bool scram_verify_plain_password(const char *username,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..ec79fafabf 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -334,6 +334,7 @@ PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("passname", PASSNAME, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index c7bc648f46..a543c18188 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,7 +43,7 @@ enum SysCacheIdentifier
AUTHMEMROLEMEM,
AUTHNAME,
AUTHOID,
- AUTHPASSWORD,
+ AUTHPASSWORDNAME,
CASTSOURCETARGET,
CLAAMNAMENSP,
CLAOID,
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 4ffc41a545..11fb244021 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -26,7 +26,7 @@ CREATE ROLE regress_passwd4 PASSWORD NULL;
-- run. Use a regular expression to mask the changing parts.
SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
- LEFT JOIN pg_auth_password p
+ LEFT JOIN pg_auth_password p
ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
ORDER BY rolname, password;
@@ -44,7 +44,7 @@ NOTICE: MD5 password cleared because of role rename
-- md5 entry should have been removed
SELECT rolname, password
FROM pg_authid
- LEFT JOIN pg_auth_password p
+ LEFT JOIN pg_auth_password p
ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd2_new'
ORDER BY rolname, password;
@@ -78,7 +78,7 @@ CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz';
CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
- LEFT JOIN pg_auth_password p
+ LEFT JOIN pg_auth_password p
ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
ORDER BY rolname, password;
@@ -102,7 +102,7 @@ NOTICE: empty string is not a valid password, clearing password
ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
NOTICE: empty string is not a valid password, clearing password
SELECT password FROM pg_authid
-LEFT JOIN pg_auth_password p
+LEFT JOIN pg_auth_password p
ON pg_authid.oid = p.roleid WHERE rolname='regress_passwd_empty';
password
----------
@@ -120,7 +120,7 @@ CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
-- should not contain the original salt.
SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
- LEFT JOIN pg_auth_password p
+ LEFT JOIN pg_auth_password p
ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
@@ -146,7 +146,7 @@ DROP ROLE regress_passwd_sha_len2;
-- all entries should have been removed
SELECT rolname, password
FROM pg_authid
- LEFT JOIN pg_auth_password p
+ LEFT JOIN pg_auth_password p
ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
ORDER BY rolname, password;
--
2.34.1
From 80f2b1948c299cbc799901d36a23d44bee36e00b Mon Sep 17 00:00:00 2001
From: Stephen Frost <[email protected]>
Date: Thu, 30 Jun 2022 18:17:50 -0400
Subject: [PATCH 3/3] Per-password expiration
To build on the multi-password support this
adds per-password expiration, either passed in via ALTER ROLE/CREATE
ROLE with the grammar EXPIRES IN, or via a system-wide setting
called password_valid_duration
Author: Joshua Brindle
---
src/backend/commands/user.c | 121 ++++++++++++++++++++++++++++++--
src/backend/commands/variable.c | 105 +++++++++++++++++++++++++++
src/backend/libpq/auth.c | 9 ++-
src/backend/libpq/crypt.c | 38 ++++++++--
src/backend/parser/gram.y | 10 ++-
src/backend/utils/misc/guc.c | 12 ++++
src/include/commands/user.h | 3 +
src/include/commands/variable.h | 4 ++
src/include/parser/kwlist.h | 1 +
9 files changed, 290 insertions(+), 13 deletions(-)
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 94747d7c3c..9c46af5123 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -47,8 +47,9 @@
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
-/* GUC parameter */
+/* GUC parameters */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
+Interval *default_password_duration = NULL;
/* default password name */
const char* default_passname = "__def__";
@@ -166,6 +167,84 @@ validate_and_get_salt(char *rolename, char **salt, const char **logdetail)
return true;
}
+static
+Datum
+expires_in_datum(DefElem *passExpiresIn)
+{
+ Interval *interval;
+ Node *arg;
+ A_Const *con;
+ TimestampTz now = GetCurrentTimestamp();
+ Datum passExpiresIn_datum;
+ char *dateout;
+
+ if (default_password_duration != NULL)
+ {
+ /* The default duration GUC is set, use it if nothing came from SQL
+ * or if something came from SQL, reject it if not from a superuser
+ */
+
+ if (passExpiresIn != NULL)
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to override password_validity_duration GUC")));
+ else
+ goto bypass;
+ else
+ {
+ passExpiresIn_datum = DirectFunctionCall2(timestamptz_pl_interval,
+ TimestampGetDatum(now),
+ PointerGetDatum(default_password_duration));
+
+ dateout =
+ DatumGetCString(DirectFunctionCall1(timestamp_out,
+ passExpiresIn_datum));
+
+ ereport(NOTICE,
+ (errmsg("Password will expire at: \"%s\" (from GUC)", dateout)));
+
+ return passExpiresIn_datum;
+ }
+ }
+
+ if (passExpiresIn == NULL) /* No duration requested via SQL and no system default, no expiration */
+ return PointerGetDatum(NULL);
+
+bypass:
+ arg = (Node *)passExpiresIn->arg;
+ if (IsA(arg, TypeCast))
+ {
+ TypeCast *tc = (TypeCast *) passExpiresIn->arg;
+ arg = tc->arg;
+ }
+
+ if (!IsA(arg, A_Const))
+ {
+ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg));
+ return PointerGetDatum(NULL);
+ }
+ con = (A_Const *) arg;
+
+ interval = DatumGetIntervalP(DirectFunctionCall3(interval_in,
+ CStringGetDatum(strVal(&con->val)),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1)));
+
+ passExpiresIn_datum = DirectFunctionCall2(timestamptz_pl_interval,
+ TimestampGetDatum(now),
+ PointerGetDatum(interval));
+
+ dateout =
+ DatumGetCString(DirectFunctionCall1(timestamp_out,
+ passExpiresIn_datum));
+
+ ereport(NOTICE,
+ (errmsg("Password will expire at: \"%s\" (from SQL)", dateout)));
+
+ return passExpiresIn_datum;
+}
+
/*
* CREATE ROLE
*/
@@ -210,7 +289,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
DefElem *dpassName = NULL;
-
+ DefElem *dpassExpiresIn = NULL;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -319,6 +398,12 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dpassName = defel;
}
+ else if (strcmp(defel->defname, "expiresin") == 0)
+ {
+ if (dpassExpiresIn)
+ errorConflictingDefElem(defel, pstate);
+ dpassExpiresIn = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
@@ -507,6 +592,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
Relation pg_auth_password_rel;
TupleDesc pg_auth_password_dsc;
HeapTuple new_tuple;
+ Datum passExpiresIn_datum;
/*
* Don't allow an empty password. Libpq treats an empty password the
@@ -528,19 +614,27 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
}
else
{
+ MemSet(new_password_record, 0, sizeof(new_password_record));
+ MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
+
+ passExpiresIn_datum = expires_in_datum(dpassExpiresIn);
+ if (passExpiresIn_datum != PointerGetDatum(NULL))
+ new_password_record[Anum_pg_auth_password_expiration - 1] = passExpiresIn_datum;
+ else
+ new_password_record_nulls[Anum_pg_auth_password_expiration - 1] = true;
+
/* Encrypt the password to the requested format. */
validate_and_get_salt(stmt->role, &salt, &logdetail);
shadow_pass = encrypt_password(Password_encryption, salt,
password);
- MemSet(new_password_record, 0, sizeof(new_password_record));
- MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
if (passname != NULL)
new_password_record[Anum_pg_auth_password_name - 1] =
DirectFunctionCall1(namein, CStringGetDatum(passname));
else
new_password_record[Anum_pg_auth_password_name - 1] =
DirectFunctionCall1(namein, CStringGetDatum(default_passname));
+
/* open password table and insert it. */
pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
@@ -647,6 +741,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
+ Datum passExpiresIn_datum; /* Time period until password expires */
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
@@ -659,6 +754,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
DefElem *dpassName = NULL;
+ DefElem *dpassExpiresIn = NULL;
Oid roleid;
check_rolespec_name(stmt->role,
@@ -742,6 +838,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dpassName = defel;
}
+ else if (strcmp(defel->defname, "expiresin") == 0)
+ {
+ if (dpassExpiresIn)
+ errorConflictingDefElem(defel, pstate);
+ dpassExpiresIn = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -762,6 +864,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (dpassName)
passname = strVal(dpassName->arg);
+
/*
* Scan the pg_authid relation to be certain the user exists.
*/
@@ -848,6 +951,13 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
MemSet(new_password_record_nulls, false, sizeof(new_password_record_nulls));
MemSet(new_password_record_repl, false, sizeof(new_password_record_repl));
+ passExpiresIn_datum = expires_in_datum(dpassExpiresIn);
+ if (passExpiresIn_datum != PointerGetDatum(NULL)) {
+ new_password_record[Anum_pg_auth_password_expiration - 1] = passExpiresIn_datum;
+ new_password_record_repl[Anum_pg_auth_password_expiration - 1] = true;
+ }
+ else
+ new_password_record_nulls[Anum_pg_auth_password_expiration - 1] = true;
/*
* issuper/createrole/etc
@@ -957,7 +1067,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
- if (new_password_record_repl[Anum_pg_auth_password_password - 1] == true)
+ if (new_password_record_repl[Anum_pg_auth_password_password - 1] == true
+ || new_password_record_repl[Anum_pg_auth_password_expiration - 1] == true)
{
Relation pg_auth_password_rel;
TupleDesc pg_auth_password_dsc;
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index e5ddcda0b4..ca85a10b80 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -24,6 +24,7 @@
#include "access/xlog.h"
#include "catalog/pg_authid.h"
#include "commands/variable.h"
+#include "commands/user.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/acl.h"
@@ -933,3 +934,107 @@ show_role(void)
/* Otherwise we can just use the GUC string */
return role_string ? role_string : "none";
}
+
+
+/*
+ * check_password_duration: GUC check_hook for password_duration
+ */
+bool
+check_password_duration(char **newval, void **extra, GucSource source)
+{
+ Interval *new_interval;
+ char *endptr;
+
+ const char *valueptr = *newval;
+ char *val;
+ Interval *interval;
+
+ if (newval == NULL || *newval == NULL) {
+ extra = NULL;
+ return true;
+ }
+
+ elog(NOTICE,"Setting password duration to \"%s\"",
+ *newval);
+
+ while (isspace((unsigned char) *valueptr))
+ valueptr++;
+ if (*valueptr != '\'') {
+ val = pstrdup(valueptr);
+ }
+ else
+ {
+ valueptr++;
+ 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) {
+ return false;
+ }
+
+ new_interval = malloc(sizeof(Interval));
+ memcpy(new_interval, interval, sizeof(Interval));
+ pfree(interval);
+
+ /*
+ * Pass back data for assign_password_validity to use
+ */
+ *extra = malloc(sizeof(Interval *));
+ if (!*extra)
+ return false;
+ *((Interval **) *extra) = new_interval;
+
+ return true;
+}
+
+/*
+ * assign_password_validity: GUC assign_hook for timezone
+ */
+void
+assign_password_duration(const char *newval, void *extra)
+{
+ if (extra == NULL)
+ default_password_duration = NULL;
+ else
+ default_password_duration = *((Interval **) extra);
+}
+
+/*
+ * show_password_validity: GUC show_hook for timezone
+ */
+const char *
+show_password_duration(void)
+{
+ const char *intervalout;
+ if (default_password_duration == NULL) {
+ return "";
+ }
+ intervalout = DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(default_password_duration)));
+
+ if (intervalout != NULL)
+ return intervalout;
+
+ return "";
+}
\ No newline at end of file
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 4cc59d39ce..857c58027c 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -860,9 +860,14 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
auth_result = CheckSASLAuth(&pg_be_scram_mech, port, (const char **) passwords, num,
logdetail);
- for (i = 0; i < num; i++)
- pfree(passwords[i]);
+ for (i = 0; i < num; i++) {
+ if (passwords[i] != NULL)
+ pfree(passwords[i]);
+ else
+ ereport(LOG,
+ (errmsg("Password %d was null", i)));
+ }
pfree(passwords);
}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 09b6bebf40..ae3ca1175f 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -40,10 +40,12 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
{
HeapTuple roleTup;
Datum datum;
+ TimestampTz current, vuntil = 0;
+
bool isnull;
char **passwords;
CatCList *passlist;
- int i;
+ int i, j = 0, num_valid_passwords = 0;
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
@@ -51,6 +53,7 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
{
*logdetail = psprintf(_("Role \"%s\" does not exist."),
role);
+ *num = 0;
return NULL; /* no such user */
}
@@ -58,7 +61,6 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
ReleaseSysCache(roleTup);
/* Find any existing password that is not the one being updated to get the salt */
passlist = SearchSysCacheList1(AUTHPASSWORDNAME, datum);
- *num = passlist->n_members;
if (passlist->n_members == 0)
{
@@ -69,15 +71,41 @@ get_role_passwords(const char *role, const char **logdetail, int *num)
return NULL; /* user has no password */
}
- passwords = palloc0(sizeof(char *) * passlist->n_members);
+ current = GetCurrentTimestamp();
+
+ for (i = 0; i < passlist->n_members; i++)
+ {
+ HeapTuple tup = &passlist->members[i]->tuple;
+
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_expiration, &isnull);
+
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ if (isnull || vuntil > current)
+ num_valid_passwords++;
+ }
+
+ passwords = palloc0(sizeof(char *) * num_valid_passwords);
+ *num = num_valid_passwords;
for (i = 0; i < passlist->n_members; i++)
{
HeapTuple tup = &passlist->members[i]->tuple;
datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
- Anum_pg_auth_password_password, &isnull);
- passwords[i] = TextDatumGetCString(datum);
+ Anum_pg_auth_password_expiration, &isnull);
+
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ if (isnull || vuntil > current)
+ {
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_password, &isnull);
+ passwords[j++] = pstrdup(TextDatumGetCString(datum));
+ }
}
ReleaseCatCacheList(passlist);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7c1d023b7e..1f00a0aeaf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -790,7 +790,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DOUBLE_P DROP
EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE
- EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
+ EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPIRES EXPLAIN EXPRESSION
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
@@ -1266,6 +1266,12 @@ AlterOptRoleElem:
$$ = makeDefElem("passname",
(Node *)makeString($2), @1);
}
+ | EXPIRES IN_P Sconst opt_interval
+ {
+ TypeName *t = SystemTypeName("interval");
+ t->typmods = $4;
+ $$ = makeDefElem("expiresin", (Node *)makeStringConstCast($3, @3, t), @1);
+ }
| VALID FOR Sconst
{
$$ = makeDefElem("validFor", (Node *)makeString($3), @1);
@@ -17766,6 +17772,7 @@ unreserved_keyword:
| EXCLUDING
| EXCLUSIVE
| EXECUTE
+ | EXPIRES
| EXPLAIN
| EXPRESSION
| EXTENSION
@@ -18340,6 +18347,7 @@ bare_label_keyword:
| EXCLUSIVE
| EXECUTE
| EXISTS
+ | EXPIRES
| EXPLAIN
| EXPRESSION
| EXTENSION
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index a7cc49898b..0d4c2c5d79 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -716,6 +716,7 @@ static char *recovery_target_string;
static char *recovery_target_xid_string;
static char *recovery_target_name_string;
static char *recovery_target_lsn_string;
+static char *password_duration_string;
/* should be static, but commands/variable.c needs to get at this */
@@ -4721,6 +4722,17 @@ static struct config_string ConfigureNamesString[] =
check_backtrace_functions, assign_backtrace_functions, NULL
},
+ {
+ {"password_valid_duration", PGC_SUSET, CONN_AUTH_AUTH,
+ gettext_noop("Specifies the default validity duration of new passwords."),
+ NULL,
+ GUC_SUPERUSER_ONLY | GUC_NOT_IN_SAMPLE
+ },
+ &password_duration_string,
+ NULL,
+ check_password_duration, assign_password_duration, show_password_duration
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 1c14f60a05..13d3479fac 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -19,6 +19,9 @@
/* GUC. Is actually of type PasswordType. */
extern PGDLLIMPORT int Password_encryption;
+/* GUC. system-wide password validity duration */
+extern Interval *default_password_duration;
+
/* Hook to check passwords in CreateRole() and AlterRole() */
typedef void (*check_password_hook_type) (const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null);
diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h
index 0e5ddcbcf3..3b941c554c 100644
--- a/src/include/commands/variable.h
+++ b/src/include/commands/variable.h
@@ -34,5 +34,9 @@ extern void assign_session_authorization(const char *newval, void *extra);
extern bool check_role(char **newval, void **extra, GucSource source);
extern void assign_role(const char *newval, void *extra);
extern const char *show_role(void);
+extern bool check_password_duration(char **newval, void **extra, GucSource source);
+extern void assign_password_duration(const char *newval, void *extra);
+extern const char *show_password_duration(void);
+
#endif /* VARIABLE_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ec79fafabf..cebf01840b 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -163,6 +163,7 @@ PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("expires", EXPIRES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("expression", EXPRESSION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD, BARE_LABEL)
--
2.34.1
[application/pgp-signature] signature.asc (819B, ../../[email protected]/3-signature.asc)
download
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2022-07-01 14:50 Brindle, Joshua <[email protected]>
parent: Stephen Frost <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Brindle, Joshua @ 2022-07-01 14:50 UTC (permalink / raw)
To: Stephen Frost <[email protected]>; Gurjeet Singh <[email protected]>; +Cc: Jacob Champion <[email protected]>; pgsql-hackers
On 6/30/22 8:20 PM, Stephen Frost wrote:
> Greetings,
>
> * Gurjeet Singh ([email protected]) wrote:
>> I am planning on picking it up next week; right now picking up steam,
>> and reviewing a different, smaller patch.
> Great! Glad that others are interested in this.
>
>> At his behest, I had a conversation with Joshua (OP), and have his
>> support to pick up and continue working on this patch. I have a some
>> ideas of my own, on what this patch should do, but since I haven't
>> fully reviewed the (bulky) patch, I'll reserve my proposals until I
>> wrap my head around it.
> I'd be curious as to your thought as to what the patch should be doing.
> Joshua and I had discussed it at some length as he was working on it.
Adding myself to the CC list here /waves
I gave Gurjeet a bit of a brain dump on what I had planned (and what
we'd talked about), though he's free to take it in a different direction
if he wants.
>> Please expect some activity on this patch towards the end of next week.
> I've gone ahead and updated it, cleaned up a couple things, and make it
> so that check-world actually passes with it. Attached is an updated
> version and I'll add it to the July commitfest.
Ah, thanks. Hopefully it wasn't too horrible of a rebase.
> Thanks!
>
> Stephen
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2022-07-01 15:13 Stephen Frost <[email protected]>
parent: Brindle, Joshua <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Stephen Frost @ 2022-07-01 15:13 UTC (permalink / raw)
To: Brindle, Joshua <[email protected]>; +Cc: Gurjeet Singh <[email protected]>; Jacob Champion <[email protected]>; pgsql-hackers
Greetings,
On Fri, Jul 1, 2022 at 10:51 Brindle, Joshua <[email protected]> wrote:
>
> On 6/30/22 8:20 PM, Stephen Frost wrote:
> > * Gurjeet Singh ([email protected]) wrote:
> >> I am planning on picking it up next week; right now picking up steam,
> >> and reviewing a different, smaller patch.
> > Great! Glad that others are interested in this.
> >
> >> At his behest, I had a conversation with Joshua (OP), and have his
> >> support to pick up and continue working on this patch. I have a some
> >> ideas of my own, on what this patch should do, but since I haven't
> >> fully reviewed the (bulky) patch, I'll reserve my proposals until I
> >> wrap my head around it.
> > I'd be curious as to your thought as to what the patch should be doing.
> > Joshua and I had discussed it at some length as he was working on it.
>
>
> Adding myself to the CC list here /waves
Hi!
I gave Gurjeet a bit of a brain dump on what I had planned (and what
> we'd talked about), though he's free to take it in a different direction
> if he wants.
Perhaps though would certainly like this to patch to be useful for the
use-cases that we had discussed, naturally. :)
>> Please expect some activity on this patch towards the end of next week.
> > I've gone ahead and updated it, cleaned up a couple things, and make it
> > so that check-world actually passes with it. Attached is an updated
> > version and I'll add it to the July commitfest.
>
> Ah, thanks. Hopefully it wasn't too horrible of a rebase.
Wasn’t too bad.. needs more clean-up, there was some white space issues and
some simple re-base stuff, but then the support for “md5” pg_hba option was
broken for users with SCRAM passwords because we weren’t checking if there
was a SCRAM pw stored and upgrading to SCRAM in that case. That’s the main
case that I fixed. We will need to document this though, of course. The
patch I submitted should basically do:
pg_hba md5 + md5-only pws -> md5 auth used
pg_hba md5 + scram-only pws -> scram
pg_hba md5 + md5 and scram pws -> scram
pg_hba scram -> scram
Not sure if we need to try and do something to make it possible to have
pg_hba md5 + mixed pws and have md5 used but it’s tricky as we would have
to know on the server side early on if that’s what we want to do. We could
add an option to md5 to say “only do md5” maybe but I’m also inclined to
not bother and tell people to just get moved to scram already.
For my 2c, I’d also like to move to having a separate column for the PW
type from the actual secret but that’s largely an independent change.
Thanks!
Stephen
>
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v1 5/7] Row pattern recognition patch (docs).
@ 2023-06-25 11:48 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-06-25 11:48 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 51 ++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 69 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 18 ++++++++--
3 files changed, 136 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..23ee285b40 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,57 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Window function <function>rpr</function> can be used with row pattern
+ common syntax to perform row pattern recognition in a query. Row pattern
+ common syntax includes two sub clauses. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. Moreover if the expression comprises a column
+ reference, it must be the argument of <function>rpr</function>. An
+ example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns price column in the previous
+ row if it's called in a context of row pattern recognition. So in the
+ second line means the definition variable "UP" is <literal>TRUE</literal>
+ when price column in the current row is greater than the price column in
+ the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". If a
+ sequence of rows found, rpr returns the column at the starting row.
+ Example of a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price, rpr(price) OVER w FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5a47ce4343..8069c58ca5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21648,6 +21648,22 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>rpr</primary>
+ </indexterm>
+ <function>rpr</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Perform row pattern recognition using column specified
+ by <parameter>value</parameter> and returns the value of the column if
+ current row is the first matching row;
+ returns <literal>NULL</literal> otherwise.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -21687,6 +21703,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 0ee0cc7e64..c0fc16d773 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -966,8 +966,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1074,6 +1074,20 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses.
+
+<synopsis>
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Sun_Jun_25_21_05_09_2023_126)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v1-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v2 5/7] Row pattern recognition patch (docs).
@ 2023-06-26 08:05 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-06-26 08:05 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 52 +++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 69 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 29 +++++++++++++--
3 files changed, 148 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..e9bbd5bc7c 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,58 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Window function <function>rpr</function> can be used with row pattern
+ common syntax to perform row pattern recognition in a query. Row pattern
+ common syntax includes two sub clauses. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. Moreover if the expression comprises a column
+ reference, it must be the argument of <function>rpr</function>. An
+ example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns price column in the previous
+ row if it's called in a context of row pattern recognition. So in the
+ second line means the definition variable "UP" is <literal>TRUE</literal>
+ when price column in the current row is greater than the price column in
+ the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". If a
+ sequence of rows found, rpr returns the column at the starting row.
+ Example of a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price, rpr(price) OVER w FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5a47ce4343..8069c58ca5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21648,6 +21648,22 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>rpr</primary>
+ </indexterm>
+ <function>rpr</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Perform row pattern recognition using column specified
+ by <parameter>value</parameter> and returns the value of the column if
+ current row is the first matching row;
+ returns <literal>NULL</literal> otherwise.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -21687,6 +21703,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 0ee0cc7e64..16478a3950 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -966,8 +966,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1074,6 +1074,31 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Mon_Jun_26_17_45_07_2023_724)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v2-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v2] Compare only major versions in AdjustUpgrade.pm
@ 2023-07-19 15:54 Alvaro Herrera <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Alvaro Herrera @ 2023-07-19 15:54 UTC (permalink / raw)
Because PostgreSQL::Version is very nuanced about development version
numbers, the comparison to 16beta2 makes it think that that release is
older than 16, therefore applying a database tweak that doesn't work
there. Fix by having AdjustUpgrade create a separate
PostgreSQL::Version object that only contains the major version number.
While at it, have it ensure that the objects given are of the expected
type.
Co-authored-by: Andrew Dunstan <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
index a241d2ceff..64305fdcce 100644
--- a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
+++ b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
@@ -76,6 +76,10 @@ sub adjust_database_contents
my ($old_version, %dbnames) = @_;
my $result = {};
+ die "wrong type for \$old_version\n"
+ unless $old_version->isa("PostgreSQL::Version");
+ $old_version = PostgreSQL::Version->new($old_version->major);
+
# remove dbs of modules known to cause pg_upgrade to fail
# anything not builtin and incompatible should clean up its own db
foreach my $bad_module ('test_ddl_deparse', 'tsearch2')
@@ -262,6 +266,10 @@ sub adjust_old_dumpfile
{
my ($old_version, $dump) = @_;
+ die "wrong type for \$old_version\n"
+ unless $old_version->isa("PostgreSQL::Version");
+ $old_version = PostgreSQL::Version->new($old_version->major);
+
# use Unix newlines
$dump =~ s/\r\n/\n/g;
@@ -579,6 +587,10 @@ sub adjust_new_dumpfile
{
my ($old_version, $dump) = @_;
+ die "wrong type for \$old_version\n"
+ unless $old_version->isa("PostgreSQL::Version");
+ $old_version = PostgreSQL::Version->new($old_version->major);
+
# use Unix newlines
$dump =~ s/\r\n/\n/g;
--
2.39.2
--tiabofo6nwpc5kic--
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v2] Compare only major versions in AdjustUpgrade.pm
@ 2023-07-19 15:54 Alvaro Herrera <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Alvaro Herrera @ 2023-07-19 15:54 UTC (permalink / raw)
Because PostgreSQL::Version is very nuanced about development version
numbers, the comparison to 16beta2 makes it think that that release is
older than 16, therefore applying a database tweak that doesn't work
there. Fix by having AdjustUpgrade create a separate
PostgreSQL::Version object that only contains the major version number.
While at it, have it ensure that the objects given are of the expected
type.
Co-authored-by: Andrew Dunstan <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
index a241d2ceff..64305fdcce 100644
--- a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
+++ b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm
@@ -76,6 +76,10 @@ sub adjust_database_contents
my ($old_version, %dbnames) = @_;
my $result = {};
+ die "wrong type for \$old_version\n"
+ unless $old_version->isa("PostgreSQL::Version");
+ $old_version = PostgreSQL::Version->new($old_version->major);
+
# remove dbs of modules known to cause pg_upgrade to fail
# anything not builtin and incompatible should clean up its own db
foreach my $bad_module ('test_ddl_deparse', 'tsearch2')
@@ -262,6 +266,10 @@ sub adjust_old_dumpfile
{
my ($old_version, $dump) = @_;
+ die "wrong type for \$old_version\n"
+ unless $old_version->isa("PostgreSQL::Version");
+ $old_version = PostgreSQL::Version->new($old_version->major);
+
# use Unix newlines
$dump =~ s/\r\n/\n/g;
@@ -579,6 +587,10 @@ sub adjust_new_dumpfile
{
my ($old_version, $dump) = @_;
+ die "wrong type for \$old_version\n"
+ unless $old_version->isa("PostgreSQL::Version");
+ $old_version = PostgreSQL::Version->new($old_version->major);
+
# use Unix newlines
$dump =~ s/\r\n/\n/g;
--
2.39.2
--tiabofo6nwpc5kic--
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v3 5/7] Row pattern recognition patch (docs).
@ 2023-07-26 10:49 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-07-26 10:49 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 52 ++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 29 +++++++++++++++++--
3 files changed, 133 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..eda3612822 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,58 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used with row pattern common syntax to
+ perform row pattern recognition in a query. Row pattern common syntax
+ includes two sub clauses. <literal>DEFINE</literal> defines definition
+ variables along with an expression. The expression must be a logical
+ expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. Moreover if the expression comprises a column
+ reference, it must be the argument of <function>rpr</function>. An example
+ of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns price column in the previous
+ row if it's called in a context of row pattern recognition. So in the
+ second line means the definition variable "UP" is <literal>TRUE</literal>
+ when price column in the current row is greater than the price column in
+ the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". If a
+ sequence of rows found, rpr returns the column at the starting row.
+ Example of a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price, max(price) OVER w FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index dcc9d6f59d..b333d62410 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21714,6 +21714,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21753,6 +21754,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 0ee0cc7e64..16478a3950 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -966,8 +966,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1074,6 +1074,31 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Wed_Jul_26_21_21_34_2023_317)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v3-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v4 5/7] Row pattern recognition patch (docs).
@ 2023-08-09 07:56 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-08-09 07:56 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 52 ++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 +++++++++++++++++++++++--
3 files changed, 142 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..eda3612822 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,58 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used with row pattern common syntax to
+ perform row pattern recognition in a query. Row pattern common syntax
+ includes two sub clauses. <literal>DEFINE</literal> defines definition
+ variables along with an expression. The expression must be a logical
+ expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. Moreover if the expression comprises a column
+ reference, it must be the argument of <function>rpr</function>. An example
+ of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns price column in the previous
+ row if it's called in a context of row pattern recognition. So in the
+ second line means the definition variable "UP" is <literal>TRUE</literal>
+ when price column in the current row is greater than the price column in
+ the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". If a
+ sequence of rows found, rpr returns the column at the starting row.
+ Example of a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price, max(price) OVER w FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index be2f54c914..6cda164522 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21715,6 +21715,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21754,6 +21755,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 0ee0cc7e64..8d3becd57a 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -966,8 +966,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1074,6 +1074,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Wed_Aug__9_17_41_12_2023_134)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v4-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v5 5/7] Row pattern recognition patch (docs).
@ 2023-09-02 06:32 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-09-02 06:32 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 52 ++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 +++++++++++++++++++++++--
3 files changed, 142 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..eda3612822 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,58 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used with row pattern common syntax to
+ perform row pattern recognition in a query. Row pattern common syntax
+ includes two sub clauses. <literal>DEFINE</literal> defines definition
+ variables along with an expression. The expression must be a logical
+ expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. Moreover if the expression comprises a column
+ reference, it must be the argument of <function>rpr</function>. An example
+ of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns price column in the previous
+ row if it's called in a context of row pattern recognition. So in the
+ second line means the definition variable "UP" is <literal>TRUE</literal>
+ when price column in the current row is greater than the price column in
+ the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". If a
+ sequence of rows found, rpr returns the column at the starting row.
+ Example of a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price, max(price) OVER w FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7a0d4b9134..b7bfc9271e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21772,6 +21772,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21811,6 +21812,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 0ee0cc7e64..8d3becd57a 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -966,8 +966,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1074,6 +1074,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Sat_Sep__2_15_52_35_2023_273)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v5-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v6 5/7] Row pattern recognition patch (docs).
@ 2023-09-12 05:22 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-09-12 05:22 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 52 ++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 +++++++++++++++++++++++--
3 files changed, 142 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..eda3612822 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,58 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used with row pattern common syntax to
+ perform row pattern recognition in a query. Row pattern common syntax
+ includes two sub clauses. <literal>DEFINE</literal> defines definition
+ variables along with an expression. The expression must be a logical
+ expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. Moreover if the expression comprises a column
+ reference, it must be the argument of <function>rpr</function>. An example
+ of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns price column in the previous
+ row if it's called in a context of row pattern recognition. So in the
+ second line means the definition variable "UP" is <literal>TRUE</literal>
+ when price column in the current row is greater than the price column in
+ the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". If a
+ sequence of rows found, rpr returns the column at the starting row.
+ Example of a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price, max(price) OVER w FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f910..9c99dda4ae 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21780,6 +21780,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21819,6 +21820,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 0ee0cc7e64..8d3becd57a 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -966,8 +966,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1074,6 +1074,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Tue_Sep_12_15_18_43_2023_359)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v6-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v7 5/7] Row pattern recognition patch (docs).
@ 2023-09-22 04:53 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-09-22 04:53 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 52 ++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 +++++++++++++++++++++++--
3 files changed, 142 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..f39ec8f2d5 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,58 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used with row pattern common syntax to
+ perform row pattern recognition in a query. Row pattern common syntax
+ includes two sub clauses. <literal>DEFINE</literal> defines definition
+ variables along with an expression. The expression must be a logical
+ expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. Moreover if the expression comprises a column
+ reference, it must be the argument of <function>rpr</function>. An example
+ of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns price column in the previous
+ row if it's called in a context of row pattern recognition. So in the
+ second line means the definition variable "UP" is <literal>TRUE</literal>
+ when price column in the current row is greater than the price column in
+ the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". If a
+ sequence of rows found, rpr returns the column at the starting row.
+ Example of a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price, max(price) OVER w FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f910..9c99dda4ae 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21780,6 +21780,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21819,6 +21820,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 0ee0cc7e64..056768b330 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -966,8 +966,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1074,6 +1074,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Fri_Sep_22_14_16_40_2023_530)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v7-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v8 5/7] Row pattern recognition patch (docs).
@ 2023-09-25 05:01 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-09-25 05:01 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 52 ++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 +++++++++++++++++++++++--
3 files changed, 142 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..f39ec8f2d5 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,58 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used with row pattern common syntax to
+ perform row pattern recognition in a query. Row pattern common syntax
+ includes two sub clauses. <literal>DEFINE</literal> defines definition
+ variables along with an expression. The expression must be a logical
+ expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. Moreover if the expression comprises a column
+ reference, it must be the argument of <function>rpr</function>. An example
+ of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns price column in the previous
+ row if it's called in a context of row pattern recognition. So in the
+ second line means the definition variable "UP" is <literal>TRUE</literal>
+ when price column in the current row is greater than the price column in
+ the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". If a
+ sequence of rows found, rpr returns the column at the starting row.
+ Example of a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price, max(price) OVER w FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f910..9c99dda4ae 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21780,6 +21780,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21819,6 +21820,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 0ee0cc7e64..056768b330 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -966,8 +966,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1074,6 +1074,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Mon_Sep_25_14_26_30_2023_752)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v8-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2023-09-25 07:31 Gurjeet Singh <[email protected]>
parent: Stephen Frost <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Gurjeet Singh @ 2023-09-25 07:31 UTC (permalink / raw)
To: Stephen Frost <[email protected]>; +Cc: Brindle, Joshua <[email protected]>; Jacob Champion <[email protected]>; pgsql-hackers; Jeff Davis <[email protected]>; Nathan Bossart <[email protected]>
On Fri, Jul 1, 2022 at 8:13 AM Stephen Frost <[email protected]> wrote:
> On Fri, Jul 1, 2022 at 10:51 Brindle, Joshua <[email protected]> wrote:
>> On 6/30/22 8:20 PM, Stephen Frost wrote:
>> > I've gone ahead and updated it, cleaned up a couple things, and make it
>> > so that check-world actually passes with it. Attached is an updated
>> > version and I'll add it to the July commitfest.
>>
>> Ah, thanks. Hopefully it wasn't too horrible of a rebase.
>
> Wasn’t too bad.. needs more clean-up, there was some white space issues and some simple re-base stuff, but then the support for “md5” pg_hba option was broken for users with SCRAM passwords because we weren’t checking if there was a SCRAM pw stored and upgrading to SCRAM in that case. That’s the main case that I fixed. We will need to document this though, of course. The patch I submitted should basically do:
>
> pg_hba md5 + md5-only pws -> md5 auth used
> pg_hba md5 + scram-only pws -> scram
> pg_hba md5 + md5 and scram pws -> scram
> pg_hba scram -> scram
>
> Not sure if we need to try and do something to make it possible to have pg_hba md5 + mixed pws and have md5 used but it’s tricky as we would have to know on the server side early on if that’s what we want to do. We could add an option to md5 to say “only do md5” maybe but I’m also inclined to not bother and tell people to just get moved to scram already.
>
> For my 2c, I’d also like to move to having a separate column for the PW type from the actual secret but that’s largely an independent change.
The docs say this about rolpassword in case it stores SCRAM-SHA-256
encrypted password "If the password is encrypted with SCRAM-SHA-256,
it has the format SCRAM-SHA-256$... This format is the same as that
specified by RFC-5803". So I believe our hands are tied, and we
cannot change that without breaking compliance with RFC 5803.
Please see attached v4 of the patch. The patch takes care of rebase to
the master/17-devel branch, and includes some changes, too. The
rebase/merge conflicts were quite involved, since some affected files
had been removed, or even split into multiple files over the course of
the last year; resolving merge-conflicts was more of a grunt work.
The changes since V3 are (compare [1] vs. [2], Git branches linked below):
- Remove TOAST table and corresponding index from pg_authid.
- Fix memory leak/allocation bug; replace malloc() with guc_alloc().
- Fix assumptions about passed-in double-pointers to GUC handling functions.
- Remove the new function is_role_valid() and its call sites, because
I believe it made backward-incompatible change to authentication
behavior (see more below).
- Improve error handling that was missing at a few places.
- Remove unnecessary checks, like (*var != NULL) checks when we know
all callers pass a NULL by convention.
- Replace MemSet() calls with var={0} styled initialization.
- Minor edits to docs to change them from pg_authid to pg_auth_password.
More details about why I chose to remove is_role_valid() and revert to
the old code:
is_role_valid() was a new function that pulled out a small section of
code from get_role_passwords() . I don't think moving this code block
to a new function gains us anything; in fact, it now forces us to call
the new function in two new locations, which we didn't have to do
before. It has to throw the same error messages as before, to maintain
compatibility with external tools/libraries, hence it duplicates those
messages as well, which is not ideal.
Moreover, before the patch, in case of CheckPasswordAuth(), the error
(if any) would have been thrown _after_ network communication done by
sendAuthRequest() call. But with this patch, the error is thrown
before the network interaction, hence this changes the order of
network interaction and the error message. This may have security
implications, too, but I'm unable to articulate one right now.
If we really want the role-validity check to be a function of its own,
a separate patch can address that; this patch doesn't have to make
that decision.
Open question: If a client is capable of providing just md5 passwords
handshake, and because of pg_hba.conf setting, or because the role has
at least one SCRAM password (essentially the 3rd case you mention
above: pg_hba md5 + md5 and scram pws -> scram), the server will
respond with a SASL/SCRAM authentication response, and that would
break the backwards compatibility and will deny access to the client.
Does this make it necessary to use a newer libpq/client library?
Before the patch, the rolvaliduntil was used to check and complain
that the password has expired, as the docs explicitly state that
rolvaliduntil represents "Password expiry time (only used for password
authentication); null if no expiration" . Keeping that column after
the introduction of per-password expiry times now separates the
role-validity from password validity. During an internal discussion a
curiosity arose whether we can simply remove rolvaliduntil. And I
believe the answer is yes, primarily because of how the docs describe
the column. So my proposal is to remove rolvaliduntil from pg_authid,
and on a case-by-case basis, optionally replace its uses with
max(pg_auth_password.expiration).
Comments?
Next steps:
- Break the patch into smaller patches.
- Address TODO items
- Comment each new function
- Add tests
- Add/update documentation
PS: Since this is a large patch, and because in some portions the code
has been indented by a level or two (e.g. to run a `for` loop over
existing code for single-password), I have found the following Git
command to be helpful in reviewing the changes between master and this
branch,: `git diff -b --color-words -U20 origin/master...HEAD -- `
[1]: v3 patch, applied to a contemporary commit on master branch
https://github.com/gurjeet/postgres/commits/multiple_passwords_v3
[2]: main development branch, patch rebased to current master branch,
followed by many changes
https://github.com/gurjeet/postgres/commits/multiple_passwords
Best regards,
Gurjeet
http://Gurje.et
Attachments:
[application/octet-stream] multiple_passwords.v4.diff (150.0K, ../../CABwTF4XZO_429=QzZHt-1Q=KSCR2XjYG+xVy2p-PdPtun9tVdg@mail.gmail.com/2-multiple_passwords.v4.diff)
download | inline diff:
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index a72f80f033..babaae69e4 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -1281,7 +1281,7 @@ omicron bryanh guest1
<para>
<productname>PostgreSQL</productname> database passwords are
separate from operating system user passwords. The password for
- each database user is stored in the <literal>pg_authid</literal> system
+ each database user is stored in the <literal>pg_passwords</literal> system
catalog. Passwords can be managed with the SQL commands
<xref linkend="sql-createrole"/> and
<xref linkend="sql-alterrole"/>,
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 3e9994793d..8a377d3f23 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -118,7 +118,8 @@ CATALOG_HEADERS := \
pg_publication_namespace.h \
pg_publication_rel.h \
pg_subscription.h \
- pg_subscription_rel.h
+ pg_subscription_rel.h \
+ pg_auth_password.h
GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h system_fk_info.h
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 1bf6c5633c..2e8e37b59b 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -28,6 +28,7 @@
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
@@ -246,6 +247,7 @@ IsSharedRelation(Oid relationId)
{
/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
if (relationId == AuthIdRelationId ||
+ relationId == AuthPasswordRelationId ||
relationId == AuthMemRelationId ||
relationId == DatabaseRelationId ||
relationId == DbRoleSettingRelationId ||
@@ -260,6 +262,8 @@ IsSharedRelation(Oid relationId)
/* These are their indexes */
if (relationId == AuthIdOidIndexId ||
relationId == AuthIdRolnameIndexId ||
+ relationId == AuthPasswordRoleOidIndexId ||
+ relationId == AuthMemRoleMemIndexId ||
relationId == AuthMemMemRoleIndexId ||
relationId == AuthMemRoleMemIndexId ||
relationId == AuthMemOidIndexId ||
@@ -281,8 +285,8 @@ IsSharedRelation(Oid relationId)
relationId == TablespaceOidIndexId)
return true;
/* These are their toast tables and toast indexes */
- if (relationId == PgAuthidToastTable ||
- relationId == PgAuthidToastIndex ||
+ if (relationId == PgAuthPasswordToastTable ||
+ relationId == PgAuthPasswordToastIndex ||
relationId == PgDatabaseToastTable ||
relationId == PgDatabaseToastIndex ||
relationId == PgDbRoleSettingToastTable ||
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 77b06e2a7a..2c9e074607 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -40,11 +40,13 @@ CREATE VIEW pg_shadow AS
rolsuper AS usesuper,
rolreplication AS userepl,
rolbypassrls AS usebypassrls,
- rolpassword AS passwd,
+ p.password AS passwd,
rolvaliduntil AS valuntil,
setconfig AS useconfig
FROM pg_authid LEFT JOIN pg_db_role_setting s
ON (pg_authid.oid = setrole AND setdatabase = 0)
+ LEFT JOIN pg_auth_password p
+ ON (p.roleid = pg_authid.oid)
WHERE rolcanlogin;
REVOKE ALL ON pg_shadow FROM public;
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index ce77a055e5..8bdd0eb592 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -23,6 +23,7 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
#include "commands/comment.h"
@@ -30,7 +31,9 @@
#include "commands/defrem.h"
#include "commands/seclabel.h"
#include "commands/user.h"
+#include "common/scram-common.h"
#include "libpq/crypt.h"
+#include "libpq/scram.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -88,6 +91,11 @@ char *createrole_self_grant = "";
bool createrole_self_grant_enabled = false;
GrantRoleOptions createrole_self_grant_options;
+Interval *default_password_duration = NULL;
+
+/* default password name */
+static const char* default_password_name = "__def__";
+
/* Hook to check passwords in CreateRole() and AlterRole() */
check_password_hook_type check_password_hook = NULL;
@@ -125,6 +133,164 @@ have_createrole_privilege(void)
return has_createrole_privilege(GetUserId());
}
+static bool
+validate_and_get_salt(char *rolename, char **salt, const char **logdetail)
+{
+ char **current_secrets;
+ int i, num_secrets;
+ char *salt1, *salt2 = NULL;
+ PasswordType passtype;
+
+ if (Password_encryption == PASSWORD_TYPE_MD5)
+ {
+ *salt = rolename; /* md5 always uses role name, no need to look through the passwords */
+ return true;
+ }
+ else if (Password_encryption == PASSWORD_TYPE_PLAINTEXT)
+ {
+ *salt = NULL; /* Plaintext does not have a salt */
+ return true;
+ }
+
+ current_secrets = get_role_passwords(rolename, logdetail, &num_secrets);
+ if (num_secrets == 0)
+ {
+ *salt = NULL; /* No existing passwords, allow one to be generated */
+ return true;
+ }
+
+ for (i = 0; i < num_secrets; i++)
+ {
+ passtype = get_password_type(current_secrets[i]);
+
+ if (passtype == PASSWORD_TYPE_MD5 || passtype == PASSWORD_TYPE_PLAINTEXT)
+ continue; /* md5 uses rolename as salt so it is always the same and plaintext has no salt */
+ else if (passtype == PASSWORD_TYPE_SCRAM_SHA_256)
+ {
+ int iterations;
+ int key_length = 0;
+ pg_cryptohash_type hash_type;
+ uint8 stored_key[SCRAM_MAX_KEY_LEN];
+ uint8 server_key[SCRAM_MAX_KEY_LEN];
+
+ if (!parse_scram_secret(current_secrets[i], &iterations, &hash_type, &key_length,
+ &salt1, stored_key, server_key))
+ {
+ *logdetail = psprintf(_("could not parse SCRAM secret"));
+ *salt = NULL;
+ return false;
+ }
+
+ if (salt2 != NULL)
+ {
+ if (strcmp(salt1, salt2))
+ {
+ *logdetail = psprintf(_("inconsistent salts, clearing password")); // TODO: Better message
+ *salt = NULL;
+ return false;
+ }
+ }
+ else
+ salt2 = salt1;
+ }
+ }
+
+ for (i = 0; i < num_secrets; i++)
+ pfree(current_secrets[i]);
+ pfree(current_secrets);
+
+ if (salt2)
+ *salt = pstrdup(salt2);
+
+ return true;
+}
+
+static Datum
+expires_in_datum(DefElem *passExpiresIn)
+{
+ Interval *interval;
+ Node *arg;
+ A_Const *con;
+ TimestampTz now = GetCurrentTimestamp();
+ Datum passExpiresIn_datum;
+ char *dateout;
+
+ if (default_password_duration != NULL)
+ {
+ /*
+ * The default duration GUC is set, use it if nothing came from SQL.
+ * If something came from SQL, reject it unless it is from a superuser.
+ */
+ if (passExpiresIn != NULL)
+ {
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to override password_validity_duration GUC")));
+ else
+ goto bypass;
+ }
+ else
+ {
+ passExpiresIn_datum = DirectFunctionCall2(timestamptz_pl_interval,
+ TimestampGetDatum(now),
+ PointerGetDatum(default_password_duration));
+
+ dateout =
+ DatumGetCString(DirectFunctionCall1(timestamp_out,
+ passExpiresIn_datum));
+
+ /* TODO: convert to DEBUG2 */
+ ereport(NOTICE,
+ (errmsg("Password will expire at: \"%s\" (from GUC)", dateout)));
+
+ return passExpiresIn_datum;
+ }
+ }
+
+ if (passExpiresIn == NULL) /* No duration requested via SQL and no system default, no expiration */
+ return PointerGetDatum(NULL);
+
+bypass:
+ arg = (Node *)passExpiresIn->arg;
+ if (IsA(arg, TypeCast)) // TODO: Do we really need to do this? Ensure a test-case covers or obviates it.
+ {
+ TypeCast *tc = (TypeCast *) passExpiresIn->arg;
+ arg = tc->arg;
+ }
+
+ if (!IsA(arg, A_Const))
+ {
+ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg));
+ /* Keep compiler happy */
+ return PointerGetDatum(NULL);
+ }
+
+ con = (A_Const *) arg;
+ if (con->isnull || !IsA(&(con->val), String))
+ {
+ elog(ERROR, "'expires in' value must be a string");
+ /* Keep compiler happy */
+ return PointerGetDatum(NULL);
+ }
+
+ interval = DatumGetIntervalP(DirectFunctionCall3(interval_in,
+ CStringGetDatum(strVal(&con->val)),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1)));
+
+ passExpiresIn_datum = DirectFunctionCall2(timestamptz_pl_interval,
+ TimestampGetDatum(now),
+ PointerGetDatum(interval));
+
+ dateout = DatumGetCString(DirectFunctionCall1(timestamp_out,
+ passExpiresIn_datum));
+
+ ereport(NOTICE,
+ (errmsg("Password will expire at: \"%s\" (from SQL)", dateout)));
+
+ return passExpiresIn_datum;
+}
/*
* CREATE ROLE
@@ -142,6 +308,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
+ char *passname = NULL; /* name of the password for managing multiple passwords */
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
@@ -169,6 +336,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ DefElem *dpassName = NULL;
+ DefElem *dpassExpiresIn = NULL;
GrantRoleOptions popt;
/* The defaults can vary depending on the original statement type */
@@ -272,6 +441,19 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
+ else if (strcmp(defel->defname, "passname") == 0)
+ {
+ if (dpassName)
+ errorConflictingDefElem(defel, pstate);
+ dpassName = defel;
+ }
+ else if (strcmp(defel->defname, "expiresin") == 0)
+ {
+ if (dpassExpiresIn)
+ errorConflictingDefElem(defel, pstate);
+ dpassExpiresIn = defel;
+ }
+
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -309,6 +491,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
validUntil = strVal(dvalidUntil->arg);
if (dbypassRLS)
bypassrls = boolVal(dbypassRLS->arg);
+ if (dpassName)
+ passname = strVal(dpassName->arg);
/* Check some permissions first */
if (!superuser_arg(currentUserId))
@@ -415,43 +599,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit);
-
- if (password)
- {
- char *shadow_pass;
- const char *logdetail = NULL;
-
- /*
- * Don't allow an empty password. Libpq treats an empty password the
- * same as no password at all, and won't even try to authenticate. But
- * other clients might, so allowing it would be confusing. By clearing
- * the password when an empty string is specified, the account is
- * consistently locked for all clients.
- *
- * Note that this only covers passwords stored in the database itself.
- * There are also checks in the authentication code, to forbid an
- * empty password from being used with authentication methods that
- * fetch the password from an external system, like LDAP or PAM.
- */
- if (password[0] == '\0' ||
- plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
- {
- ereport(NOTICE,
- (errmsg("empty string is not a valid password, clearing password")));
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
- }
- else
- {
- /* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, stmt->role,
- password);
- new_record[Anum_pg_authid_rolpassword - 1] =
- CStringGetTextDatum(shadow_pass);
- }
- }
- else
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
-
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
@@ -486,6 +633,74 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
*/
CatalogTupleInsert(pg_authid_rel, tuple);
+ if (password)
+ {
+ char *shadow_pass, *salt;
+ const char *logdetail;
+ Datum new_password_record[Natts_pg_auth_password] = {0};
+ bool new_password_record_nulls[Natts_pg_auth_password] = {0};
+ Relation pg_auth_password_rel;
+ TupleDesc pg_auth_password_dsc;
+ HeapTuple new_tuple;
+ Datum passExpiresIn_datum;
+
+ /*
+ * Don't allow an empty password. Libpq treats an empty password the
+ * same as no password at all, and won't even try to authenticate. But
+ * other clients might, so allowing it would be confusing. By clearing
+ * the password when an empty string is specified, the account is
+ * consistently locked for all clients.
+ *
+ * Note that this only covers passwords stored in the database itself.
+ * There are also checks in the authentication code, to forbid an
+ * empty password from being used with authentication methods that
+ * fetch the password from an external system, like LDAP or PAM.
+ */
+ if (password[0] == '\0' ||
+ plain_crypt_verify(stmt->role, password, "", &logdetail) == STATUS_OK)
+ {
+ ereport(NOTICE,
+ (errmsg("empty string is not a valid password, clearing password")));
+ }
+ else
+ {
+ passExpiresIn_datum = expires_in_datum(dpassExpiresIn);
+ if (passExpiresIn_datum != PointerGetDatum(NULL))
+ new_password_record[Anum_pg_auth_password_expiration - 1] = passExpiresIn_datum;
+ else
+ new_password_record_nulls[Anum_pg_auth_password_expiration - 1] = true;
+
+ /* Encrypt the password to the requested format. */
+ if (!validate_and_get_salt(stmt->role, &salt, &logdetail))
+ ereport(ERROR,
+ (errmsg("could not get a valid salt for password"),
+ errdetail("%s", logdetail)));
+
+ shadow_pass = encrypt_password(Password_encryption, salt, password);
+
+ if (passname != NULL)
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(passname));
+ else
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(default_password_name));
+
+ /* open password table and insert it. */
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
+
+ new_password_record[Anum_pg_auth_password_password - 1] =
+ CStringGetTextDatum(shadow_pass);
+ new_password_record[Anum_pg_auth_password_roleid - 1] = roleid;
+
+ new_tuple = heap_form_tuple(pg_auth_password_dsc,
+ new_password_record, new_password_record_nulls);
+ CatalogTupleInsert(pg_auth_password_rel, new_tuple);
+ heap_freetuple(new_tuple);
+ table_close(pg_auth_password_rel, NoLock);
+ }
+ }
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -622,6 +837,9 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
Datum new_record[Natts_pg_authid] = {0};
bool new_record_nulls[Natts_pg_authid] = {0};
bool new_record_repl[Natts_pg_authid] = {0};
+ Datum new_password_record[Natts_pg_auth_password] = {0};
+ bool new_password_record_nulls[Natts_pg_auth_password] = {0};
+ bool new_password_record_repl[Natts_pg_auth_password] = {0};
Relation pg_authid_rel;
TupleDesc pg_authid_dsc;
HeapTuple tuple,
@@ -630,10 +848,12 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ListCell *option;
char *rolename;
char *password = NULL; /* user password */
+ char *passname = NULL; /* password name for managing multiple passwords */
int connlimit = -1; /* maximum connections allowed */
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
+ Datum passExpiresIn_datum; /* Time period until password expires */
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
@@ -645,6 +865,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ DefElem *dpassName = NULL;
+ DefElem *dpassExpiresIn = NULL;
Oid roleid;
Oid currentUserId = GetUserId();
GrantRoleOptions popt;
@@ -724,6 +946,18 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
errorConflictingDefElem(defel, pstate);
dbypassRLS = defel;
}
+ else if (strcmp(defel->defname, "passname") == 0)
+ {
+ if (dpassName)
+ errorConflictingDefElem(defel, pstate);
+ dpassName = defel;
+ }
+ else if (strcmp(defel->defname, "expiresin") == 0)
+ {
+ if (dpassExpiresIn)
+ errorConflictingDefElem(defel, pstate);
+ dpassExpiresIn = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
@@ -741,6 +975,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
}
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
+ if (dpassName)
+ passname = strVal(dpassName->arg);
/*
* Scan the pg_authid relation to be certain the user exists.
@@ -857,6 +1093,15 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
* Build an updated tuple, perusing the information just obtained
*/
+ passExpiresIn_datum = expires_in_datum(dpassExpiresIn);
+ if (passExpiresIn_datum != PointerGetDatum(NULL))
+ {
+ new_password_record[Anum_pg_auth_password_expiration - 1] = passExpiresIn_datum;
+ new_password_record_repl[Anum_pg_auth_password_expiration - 1] = true;
+ }
+ else
+ new_password_record_nulls[Anum_pg_auth_password_expiration - 1] = true;
+
/*
* issuper/createrole/etc
*/
@@ -915,6 +1160,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (password)
{
char *shadow_pass;
+ char *salt = NULL;
const char *logdetail = NULL;
/* Like in CREATE USER, don't allow an empty password. */
@@ -923,24 +1169,41 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
{
ereport(NOTICE,
(errmsg("empty string is not a valid password, clearing password")));
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
}
else
+ {
+ if (!validate_and_get_salt(rolename, &salt, &logdetail))
+ ereport(ERROR,
+ (errmsg("could not get a valid salt for password"),
+ errdetail("%s", logdetail)));
+
+ if (salt == NULL)
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
+ else
{
/* Encrypt the password to the requested format. */
- shadow_pass = encrypt_password(Password_encryption, rolename,
- password);
- new_record[Anum_pg_authid_rolpassword - 1] =
+ shadow_pass = encrypt_password(Password_encryption, salt, password);
+ new_password_record[Anum_pg_auth_password_password - 1] =
CStringGetTextDatum(shadow_pass);
+
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
+
+ if (passname)
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(passname));
+ else
+ new_password_record[Anum_pg_auth_password_name - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(default_password_name));
+ }
}
- new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
}
/* unset password */
if (dpassword && dpassword->arg == NULL)
{
- new_record_repl[Anum_pg_authid_rolpassword - 1] = true;
- new_record_nulls[Anum_pg_authid_rolpassword - 1] = true;
+ new_password_record_repl[Anum_pg_auth_password_password - 1] = true;
+ new_password_record_nulls[Anum_pg_auth_password_password - 1] = true;
}
/* valid until */
@@ -957,11 +1220,52 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
new_record_nulls, new_record_repl);
CatalogTupleUpdate(pg_authid_rel, &tuple->t_self, new_tuple);
+ ReleaseSysCache(tuple);
+ heap_freetuple(new_tuple);
- InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
+ if (new_password_record_repl[Anum_pg_auth_password_password - 1] == true
+ || new_password_record_repl[Anum_pg_auth_password_expiration - 1] == true)
+ {
+ Relation pg_auth_password_rel;
+ TupleDesc pg_auth_password_dsc;
+ HeapTuple password_tuple;
- ReleaseSysCache(tuple);
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+ pg_auth_password_dsc = RelationGetDescr(pg_auth_password_rel);
+ if (dpassName)
+ password_tuple = SearchSysCache2(AUTHPASSWORDNAME, ObjectIdGetDatum(roleid), CStringGetDatum(passname));
+ else
+ password_tuple = SearchSysCache2(AUTHPASSWORDNAME, ObjectIdGetDatum(roleid), CStringGetDatum(default_password_name));
+
+ if (new_password_record_nulls[Anum_pg_auth_password_password - 1] == true) /* delete existing password */
+ {
+ if (HeapTupleIsValid(password_tuple))
+ {
+ CatalogTupleDelete(pg_auth_password_rel, &password_tuple->t_self);
+ ReleaseSysCache(password_tuple);
+ }
+ }
+ else if (HeapTupleIsValid(password_tuple)) /* update existing password */
+ {
+ new_tuple = heap_modify_tuple(password_tuple, pg_auth_password_dsc, new_password_record,
+ new_password_record_nulls, new_password_record_repl);
+ CatalogTupleUpdate(pg_auth_password_rel, &password_tuple->t_self, new_tuple);
+ ReleaseSysCache(password_tuple);
heap_freetuple(new_tuple);
+ }
+ else /* insert new password */
+ {
+ new_password_record[Anum_pg_auth_password_roleid - 1] = roleid;
+
+ new_tuple = heap_form_tuple(pg_auth_password_dsc,
+ new_password_record, new_password_record_nulls);
+ CatalogTupleInsert(pg_auth_password_rel, new_tuple);
+ heap_freetuple(new_tuple);
+ }
+
+ table_close(pg_auth_password_rel, NoLock);
+ }
+ InvokeObjectPostAlterHook(AuthIdRelationId, roleid, 0);
InitGrantRoleOptions(&popt);
@@ -1083,7 +1387,6 @@ AlterRoleSet(AlterRoleSetStmt *stmt)
return roleid;
}
-
/*
* DROP ROLE
*/
@@ -1091,7 +1394,8 @@ void
DropRole(DropRoleStmt *stmt)
{
Relation pg_authid_rel,
- pg_auth_members_rel;
+ pg_auth_members_rel,
+ pg_auth_password_rel;
ListCell *item;
List *role_addresses = NIL;
@@ -1108,6 +1412,7 @@ DropRole(DropRoleStmt *stmt)
*/
pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock);
pg_auth_members_rel = table_open(AuthMemRelationId, RowExclusiveLock);
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
foreach(item, stmt->roles)
{
@@ -1249,6 +1554,29 @@ DropRole(DropRoleStmt *stmt)
systable_endscan(sscan);
+ /*
+ * Drop password(s)
+ */
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_password_roleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ sscan = systable_beginscan(pg_auth_password_rel, AuthPasswordRoleOidIndexId,
+ true, NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan)))
+ {
+ CatalogTupleDelete(pg_auth_password_rel, &tmp_tuple->t_self);
+ }
+
+ systable_endscan(sscan);
+
+ /*
+ * Remove settings for this role.
+ */
+ DropSetting(InvalidOid, roleid);
+
/*
* Advance command counter so that later iterations of this loop will
* see the changes already made. This is essential if, for example,
@@ -1331,6 +1659,7 @@ DropRole(DropRoleStmt *stmt)
* Now we can clean up; but keep locks until commit.
*/
table_close(pg_auth_members_rel, NoLock);
+ table_close(pg_auth_password_rel, NoLock);
table_close(pg_authid_rel, NoLock);
}
@@ -1341,11 +1670,15 @@ ObjectAddress
RenameRole(const char *oldname, const char *newname)
{
HeapTuple oldtuple,
- newtuple;
+ newtuple,
+ passtuple;
TupleDesc dsc;
+ ScanKeyData scankey;
+ SysScanDesc sscan;
+
Relation rel;
Datum datum;
- bool isnull;
+ bool isnull = true;
Datum repl_val[Natts_pg_authid];
bool repl_null[Natts_pg_authid];
bool repl_repl[Natts_pg_authid];
@@ -1353,6 +1686,7 @@ RenameRole(const char *oldname, const char *newname)
Oid roleid;
ObjectAddress address;
Form_pg_authid authform;
+ Relation pg_auth_password_rel;
rel = table_open(AuthIdRelationId, RowExclusiveLock);
dsc = RelationGetDescr(rel);
@@ -1449,17 +1783,38 @@ RenameRole(const char *oldname, const char *newname)
CStringGetDatum(newname));
repl_null[Anum_pg_authid_rolname - 1] = false;
- datum = heap_getattr(oldtuple, Anum_pg_authid_rolpassword, dsc, &isnull);
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_password_roleid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+
+ pg_auth_password_rel = table_open(AuthPasswordRelationId, RowExclusiveLock);
+
+ sscan = systable_beginscan(pg_auth_password_rel, AuthPasswordRoleOidIndexId,
+ true, NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(passtuple = systable_getnext(sscan)))
+ {
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, passtuple,
+ Anum_pg_auth_password_password, &isnull);
if (!isnull && get_password_type(TextDatumGetCString(datum)) == PASSWORD_TYPE_MD5)
{
+
/* MD5 uses the username as salt, so just clear it on a rename */
- repl_repl[Anum_pg_authid_rolpassword - 1] = true;
- repl_null[Anum_pg_authid_rolpassword - 1] = true;
+ if (HeapTupleIsValid(passtuple))
+ {
+ CatalogTupleDelete(pg_auth_password_rel, &passtuple->t_self);
ereport(NOTICE,
(errmsg("MD5 password cleared because of role rename")));
+
+ }
}
+ }
+
+ systable_endscan(sscan);
+ table_close(pg_auth_password_rel, NoLock);
newtuple = heap_modify_tuple(oldtuple, dsc, repl_val, repl_null, repl_repl);
CatalogTupleUpdate(rel, &oldtuple->t_self, newtuple);
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index a88cf5f118..c9b9bda5fd 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -24,6 +24,7 @@
#include "access/xlog.h"
#include "access/xlogprefetcher.h"
#include "catalog/pg_authid.h"
+#include "commands/user.h"
#include "common/string.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
@@ -1234,3 +1235,100 @@ check_ssl(bool *newval, void **extra, GucSource source)
#endif
return true;
}
+
+/*
+ * check_password_duration: GUC check_hook for password_duration
+ */
+bool
+check_password_duration(char **newval, void **extra, GucSource source)
+{
+ Interval *new_interval;
+ char *endptr;
+
+ const char *valueptr = *newval;
+ char *val;
+ Interval *interval;
+
+ if (*newval == NULL)
+ return true;
+
+ /* TODO: Change to DEBUG2 */
+ elog(NOTICE,"Setting password duration to \"%s\"", *newval);
+
+ while (isspace((unsigned char) *valueptr))
+ valueptr++;
+
+ if (*valueptr != '\'')
+ {
+ val = pstrdup(valueptr);
+ }
+ else
+ {
+ valueptr++;
+ 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)
+ return false;
+
+ new_interval = guc_malloc(LOG, sizeof(Interval));
+ memcpy(new_interval, interval, sizeof(Interval));
+ pfree(interval);
+
+ *extra = (void*) new_interval;
+
+ return true;
+}
+
+/*
+ * assign_password_validity: GUC assign_hook for password_duration
+ */
+void
+assign_password_duration(const char *newval, void *extra)
+{
+ if (extra == NULL)
+ default_password_duration = NULL;
+ else
+ default_password_duration = (Interval *) extra;
+}
+
+/*
+ * show_password_validity: GUC show_hook for password_duration
+ */
+const char *
+show_password_duration(void)
+{
+ const char *intervalout;
+
+ if (default_password_duration == NULL)
+ return "";
+
+ intervalout = DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(default_password_duration)));
+
+ if (intervalout != NULL)
+ return intervalout;
+
+ return "";
+}
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index c535bc5383..fa03615696 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -33,7 +33,7 @@
* implementation.
*
* shadow_pass is an optional pointer to the stored secret of the role
- * authenticated, from pg_authid.rolpassword. For mechanisms that use
+ * authenticated, from pg_auth_password.password. For mechanisms that use
* shadowed passwords, a NULL pointer here means that an entry could not
* be found for the role (or the user does not exist), and the mechanism
* should fail the authentication exchange.
@@ -49,7 +49,7 @@
* should just pass NULL.
*/
int
-CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
+CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, const char **passwords, int num_passwords,
const char **logdetail)
{
StringInfoData sasl_mechs;
@@ -136,7 +136,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
* This is because we don't want to reveal to an attacker what
* usernames are valid, nor which users have a valid password.
*/
- opaq = mech->init(port, selected_mech, shadow_pass);
+ opaq = mech->init(port, selected_mech, passwords, num_passwords);
inputlen = pq_getmsgint(&buf, 4);
if (inputlen == -1)
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 118d15b1a1..9c66d09206 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -109,7 +109,7 @@
static void scram_get_mechanisms(Port *port, StringInfo buf);
static void *scram_init(Port *port, const char *selected_mech,
- const char *shadow_pass);
+ const char **secrets, const int num_secrets);
static int scram_exchange(void *opaq, const char *input, int inputlen,
char **output, int *outputlen,
const char **logdetail);
@@ -132,6 +132,12 @@ typedef enum
SCRAM_AUTH_FINISHED
} scram_state_enum;
+typedef struct
+{
+ uint8 StoredKey[SCRAM_MAX_KEY_LEN];
+ uint8 ServerKey[SCRAM_MAX_KEY_LEN];
+} scram_secret;
+
typedef struct
{
scram_state_enum state;
@@ -145,10 +151,16 @@ typedef struct
pg_cryptohash_type hash_type;
int key_length;
+ /*
+ * The salt and iterations must be the same for all
+ * secrets since they are sent as part of the initial message
+ */
int iterations;
char *salt; /* base64-encoded */
- uint8 StoredKey[SCRAM_MAX_KEY_LEN];
- uint8 ServerKey[SCRAM_MAX_KEY_LEN];
+ /* Array of possible secrets */
+ scram_secret *secrets;
+ int num_secrets;
+ int chosen_secret; /* secret chosen during final client message */
/* Fields of the first message from client */
char cbind_flag;
@@ -231,17 +243,20 @@ scram_get_mechanisms(Port *port, StringInfo buf)
* It should be one of the mechanisms that we support, as returned by
* scram_get_mechanisms().
*
- * 'shadow_pass' is the role's stored secret, from pg_authid.rolpassword.
+ * 'passwords' are the role's stored secrets, from pg_auth_password.password.
* The username was provided by the client in the startup message, and is
* available in port->user_name. If 'shadow_pass' is NULL, we still perform
* an authentication exchange, but it will fail, as if an incorrect password
* was given.
*/
static void *
-scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
+scram_init(Port *port, const char *selected_mech, const char **secrets, const int num_secrets)
{
scram_state *state;
- bool got_secret;
+ bool got_secret = false;
+ int i;
+ int iterations;
+ char *salt = NULL; /* base64-encoded */
state = (scram_state *) palloc0(sizeof(scram_state));
state->port = port;
@@ -270,49 +285,54 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
/*
* Parse the stored secret.
*/
- if (shadow_pass)
+ if (secrets)
{
- int password_type = get_password_type(shadow_pass);
+ state->secrets = palloc0(sizeof(scram_secret) * num_secrets);
+ state->num_secrets = num_secrets;
+ for (i = 0; i < num_secrets; i++)
+ {
+ int password_type = get_password_type(secrets[i]);
if (password_type == PASSWORD_TYPE_SCRAM_SHA_256)
{
- if (parse_scram_secret(shadow_pass, &state->iterations,
+ if (parse_scram_secret(secrets[i], &state->iterations,
&state->hash_type, &state->key_length,
&state->salt,
- state->StoredKey,
- state->ServerKey))
- got_secret = true;
- else
+ state->secrets[i].StoredKey,
+ state->secrets[i].ServerKey))
{
- /*
- * The password looked like a SCRAM secret, but could not be
- * parsed.
- */
- ereport(LOG,
- (errmsg("invalid SCRAM secret for user \"%s\"",
+ if (salt)
+ {
+ /* The stored iterations and salt must match or we cannot proceed, allow failure via mock */
+ if (strcmp(salt, state->salt) || iterations != state->iterations)
+ {
+ ereport(WARNING, (errmsg("inconsistent salt or iterations for user \"%s\"",
state->port->user_name)));
- got_secret = false;
+ got_secret = false; /* fail and allow mock creditials to be created */
+ pfree(state->secrets);
+ state->num_secrets = 0;
+ break;
}
}
else
{
- /*
- * The user doesn't have SCRAM secret. (You cannot do SCRAM
- * authentication with an MD5 hash.)
- */
- state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM secret."),
- state->port->user_name);
- got_secret = false;
+ salt = state->salt;
+ iterations = state->iterations;
+ got_secret = true; /* We got at least one good SCRAM secret */
}
}
else
{
/*
- * The caller requested us to perform a dummy authentication. This is
- * considered normal, since the caller requested it, so don't set log
- * detail.
+ * The password looked like a SCRAM secret, but could not be
+ * parsed.
*/
- got_secret = false;
+ ereport(LOG,
+ (errmsg("invalid SCRAM secret for user \"%s\"",
+ state->port->user_name)));
+ }
+ }
+ }
}
/*
@@ -323,10 +343,13 @@ scram_init(Port *port, const char *selected_mech, const char *shadow_pass)
*/
if (!got_secret)
{
+ state->secrets = palloc0(sizeof(scram_secret));
+ state->num_secrets = 1;
+
mock_scram_secret(state->port->user_name, &state->hash_type,
&state->iterations, &state->key_length,
&state->salt,
- state->StoredKey, state->ServerKey);
+ state->secrets[0].StoredKey, state->secrets[0].ServerKey);
state->doomed = true;
}
@@ -469,12 +492,12 @@ scram_exchange(void *opaq, const char *input, int inputlen,
}
/*
- * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
+ * Construct a SCRAM secret, for storing in pg_auth_password.password.
*
* The result is palloc'd, so caller is responsible for freeing it.
*/
char *
-pg_be_scram_build_secret(const char *password)
+pg_be_scram_build_secret(const char *password, const char *salt)
{
char *prep_password;
pg_saslprep_rc rc;
@@ -491,11 +514,20 @@ pg_be_scram_build_secret(const char *password)
if (rc == SASLPREP_SUCCESS)
password = (const char *) prep_password;
- /* Generate random salt */
- if (!pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
+ /* Use passed-in salt, or generate random salt */
+ if (!salt && !pg_strong_random(saltbuf, SCRAM_DEFAULT_SALT_LEN))
+ {
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("could not generate random salt")));
+ }
+ else if (salt)
+ {
+ if (pg_b64_decode(salt, strlen(salt), saltbuf, SCRAM_DEFAULT_SALT_LEN) == -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("could not decode SCRAM salt")));
+ }
result = scram_build_secret(PG_SHA256, SCRAM_SHA_256_KEY_LEN,
saltbuf, SCRAM_DEFAULT_SALT_LEN,
@@ -1142,16 +1174,20 @@ verify_client_proof(scram_state *state)
uint8 ClientSignature[SCRAM_MAX_KEY_LEN];
uint8 ClientKey[SCRAM_MAX_KEY_LEN];
uint8 client_StoredKey[SCRAM_MAX_KEY_LEN];
- pg_hmac_ctx *ctx = pg_hmac_create(state->hash_type);
- int i;
+ pg_hmac_ctx *ctx;
+ int i, j;
const char *errstr = NULL;
-
/*
* Calculate ClientSignature. Note that we don't log directly a failure
* here even when processing the calculations as this could involve a mock
* authentication.
*/
- if (pg_hmac_init(ctx, state->StoredKey, state->key_length) < 0 ||
+ for (j = 0; j < state->num_secrets; j++)
+ {
+ ctx = pg_hmac_create(state->hash_type);
+ elog(LOG, "Trying to verify password %d", j); // TODO: Convert to DEBUG2
+
+ if (pg_hmac_init(ctx, state->secrets[j].StoredKey, state->key_length) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
@@ -1165,10 +1201,15 @@ verify_client_proof(scram_state *state)
strlen(state->client_final_message_without_proof)) < 0 ||
pg_hmac_final(ctx, ClientSignature, state->key_length) < 0)
{
- elog(ERROR, "could not calculate client signature: %s",
- pg_hmac_error(ctx));
+ // TODO: Convert to DEBUG2
+ elog(LOG, "could not calculate client signature for secret %d", j);
+ pg_hmac_free(ctx);
+ continue;
}
+ // TODO: Convert to DEBUG2
+ elog(LOG, "succeeded on %d password", j);
+
pg_hmac_free(ctx);
/* Extract the ClientKey that the client calculated from the proof */
@@ -1180,10 +1221,15 @@ verify_client_proof(scram_state *state)
client_StoredKey, &errstr) < 0)
elog(ERROR, "could not hash stored key: %s", errstr);
- if (memcmp(client_StoredKey, state->StoredKey, state->key_length) != 0)
- return false;
-
+ if (memcmp(client_StoredKey, state->secrets[j].StoredKey, state->key_length) == 0) {
+ // TODO: Convert to DEBUG2
+ elog(LOG, "Moving forward with Password %d", j);
+ state->chosen_secret = j;
return true;
+ }
+ }
+
+ return false;
}
/*
@@ -1409,7 +1455,7 @@ build_server_final_message(scram_state *state)
pg_hmac_ctx *ctx = pg_hmac_create(state->hash_type);
/* calculate ServerSignature */
- if (pg_hmac_init(ctx, state->ServerKey, state->key_length) < 0 ||
+ if (pg_hmac_init(ctx, state->secrets[state->chosen_secret].ServerKey, state->key_length) < 0 ||
pg_hmac_update(ctx,
(uint8 *) state->client_first_message_bare,
strlen(state->client_first_message_bare)) < 0 ||
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 81dabb9c27..3c18c8f616 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -57,8 +57,7 @@ static void set_authn_id(Port *port, const char *id);
static int CheckPasswordAuth(Port *port, const char **logdetail);
static int CheckPWChallengeAuth(Port *port, const char **logdetail);
-static int CheckMD5Auth(Port *port, char *shadow_pass,
- const char **logdetail);
+static int CheckMD5Auth(Port *port, const char **passwords, int num_passwords, const char **logdetail);
/*----------------------------------------------------------------
@@ -789,8 +788,9 @@ static int
CheckPasswordAuth(Port *port, const char **logdetail)
{
char *passwd;
- int result;
- char *shadow_pass;
+ int result = STATUS_ERROR;
+ int i, num_passwords;
+ char **passwords;
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
@@ -798,17 +798,21 @@ CheckPasswordAuth(Port *port, const char **logdetail)
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
- shadow_pass = get_role_password(port->user_name, logdetail);
- if (shadow_pass)
+ passwords = get_role_passwords(port->user_name, logdetail, &num_passwords);
+ if (passwords != NULL) {
+ for (i = 0; i < num_passwords; i++)
{
- result = plain_crypt_verify(port->user_name, shadow_pass, passwd,
+ result = plain_crypt_verify(port->user_name, passwords[i], passwd,
logdetail);
+ if (result == STATUS_OK)
+ break; /* Found a matching password, no need to try any others */
+ }
+ for (i = 0; i < num_passwords; i++)
+ pfree(passwords[i]);
+
+ pfree(passwords);
}
- else
- result = STATUS_ERROR;
- if (shadow_pass)
- pfree(shadow_pass);
pfree(passwd);
if (result == STATUS_OK)
@@ -823,57 +827,51 @@ CheckPasswordAuth(Port *port, const char **logdetail)
static int
CheckPWChallengeAuth(Port *port, const char **logdetail)
{
- int auth_result;
- char *shadow_pass;
- PasswordType pwtype;
+ bool scram_pw_avail = false;
+ int auth_result = STATUS_ERROR;
+ int i, num_passwords;
+ char **passwords;
Assert(port->hba->auth_method == uaSCRAM ||
port->hba->auth_method == uaMD5);
- /* First look up the user's password. */
- shadow_pass = get_role_password(port->user_name, logdetail);
-
- /*
- * If the user does not exist, or has no password or it's expired, we
- * still go through the motions of authentication, to avoid revealing to
- * the client that the user didn't exist. If 'md5' is allowed, we choose
- * whether to use 'md5' or 'scram-sha-256' authentication based on current
- * password_encryption setting. The idea is that most genuine users
- * probably have a password of that type, and if we pretend that this user
- * had a password of that type, too, it "blends in" best.
- */
- if (!shadow_pass)
- pwtype = Password_encryption;
- else
- pwtype = get_password_type(shadow_pass);
+ /* First look up the user's passwords. */
+ passwords = get_role_passwords(port->user_name, logdetail, &num_passwords);
/*
* If 'md5' authentication is allowed, decide whether to perform 'md5' or
* 'scram-sha-256' authentication based on the type of password the user
- * has. If it's an MD5 hash, we must do MD5 authentication, and if it's a
- * SCRAM secret, we must do SCRAM authentication.
+ * has. If there's a SCRAM PW available then we'll do SCRAM, otherwise we
+ * will fall back to trying to use MD5.
*
* If MD5 authentication is not allowed, always use SCRAM. If the user
* had an MD5 password, CheckSASLAuth() with the SCRAM mechanism will
* fail.
*/
- if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5)
- auth_result = CheckMD5Auth(port, shadow_pass, logdetail);
+ if (passwords != NULL)
+ {
+ for (i = 0; i < num_passwords; i++)
+ if (get_password_type(passwords[i]) == PASSWORD_TYPE_SCRAM_SHA_256)
+ {
+ scram_pw_avail = true;
+ break;
+ }
+
+ if (port->hba->auth_method == uaMD5 && !scram_pw_avail)
+ auth_result = CheckMD5Auth(port, (const char **) passwords, num_passwords, logdetail);
else
- auth_result = CheckSASLAuth(&pg_be_scram_mech, port, shadow_pass,
+ auth_result = CheckSASLAuth(&pg_be_scram_mech, port, (const char **) passwords, num_passwords,
logdetail);
- if (shadow_pass)
- pfree(shadow_pass);
-
- /*
- * If get_role_password() returned error, return error, even if the
- * authentication succeeded.
- */
- if (!shadow_pass)
+ for (i = 0; i < num_passwords; i++)
{
- Assert(auth_result != STATUS_OK);
- return STATUS_ERROR;
+ if (passwords[i] != NULL)
+ pfree(passwords[i]);
+ else
+ ereport(DEBUG2,
+ (errmsg("Password %d was null", i)));
+ }
+ pfree(passwords);
}
if (auth_result == STATUS_OK)
@@ -883,11 +881,12 @@ CheckPWChallengeAuth(Port *port, const char **logdetail)
}
static int
-CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
+CheckMD5Auth(Port *port, const char **passwords, int num_passwords, const char **logdetail)
{
char md5Salt[4]; /* Password salt */
char *passwd;
- int result;
+ int result = STATUS_ERROR;
+ int i;
/* include the salt to use for computing the response */
if (!pg_strong_random(md5Salt, 4))
@@ -903,12 +902,13 @@ CheckMD5Auth(Port *port, char *shadow_pass, const char **logdetail)
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
- if (shadow_pass)
- result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
+ for (i = 0; i < num_passwords; i++)
+ {
+ result = md5_crypt_verify(port->user_name, passwords[i], passwd,
md5Salt, 4, logdetail);
- else
- result = STATUS_ERROR;
-
+ if (result == STATUS_OK)
+ break;
+ }
pfree(passwd);
return result;
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index ef496a0bea..5880033f0c 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -2,7 +2,7 @@
*
* crypt.c
* Functions for dealing with encrypted passwords stored in
- * pg_authid.rolpassword.
+ * pg_auth_password.password.
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -16,12 +16,14 @@
#include <unistd.h>
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "common/md5.h"
#include "common/scram-common.h"
#include "libpq/crypt.h"
#include "libpq/scram.h"
#include "miscadmin.h"
#include "utils/builtins.h"
+#include "utils/catcache.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
@@ -33,53 +35,108 @@
* for the postmaster log, in *logdetail. The error reason should *not* be
* sent to the client, to avoid giving away user information!
*/
-char *
-get_role_password(const char *role, const char **logdetail)
+char **
+get_role_passwords(const char *role, const char **logdetail, int *num)
{
- TimestampTz vuntil = 0;
HeapTuple roleTup;
Datum datum;
+ TimestampTz current, vuntil = 0;
+
bool isnull;
- char *shadow_pass;
+ char **passwords;
+ CatCList *passlist;
+ int i, j = 0, num_valid_passwords = 0;
+
+ *num = 0;
/* Get role info from pg_authid */
roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
if (!HeapTupleIsValid(roleTup))
{
- *logdetail = psprintf(_("Role \"%s\" does not exist."),
- role);
+ *logdetail = psprintf(_("Role \"%s\" does not exist."), role);
return NULL; /* no such user */
}
datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolpassword, &isnull);
- if (isnull)
+ Anum_pg_authid_rolvaliduntil, &isnull);
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ current = GetCurrentTimestamp();
+
+ /*
+ * Check to be sure we are not past rolvaliduntil
+ */
+ if (!isnull && vuntil < current)
{
+ /*
+ * Although it is the role that has expired, not one of its passwords,
+ * below we complain that the password has expired. This is to maintain
+ * compatibility with external tools that may have come to depend on
+ * this message when a role expires.
+ */
+ *logdetail = psprintf(_("User \"%s\" has an expired password."),
+ role);
+ return NULL;
+ }
+
+ datum = SysCacheGetAttr(AUTHNAME, roleTup, Anum_pg_authid_oid, &isnull);
ReleaseSysCache(roleTup);
+
+ /* Find any existing password that is not the one being updated, to get the salt */
+ passlist = SearchSysCacheList1(AUTHPASSWORDNAME, datum);
+
+ if (passlist->n_members == 0)
+ {
*logdetail = psprintf(_("User \"%s\" has no password assigned."),
role);
+ ReleaseCatCacheList(passlist);
return NULL; /* user has no password */
}
- shadow_pass = TextDatumGetCString(datum);
- datum = SysCacheGetAttr(AUTHNAME, roleTup,
- Anum_pg_authid_rolvaliduntil, &isnull);
+ /*
+ * TODO: Merge this and the following loop into one; allocate
+ * valid_passwords array as long as n_members, then use repalloc() to shrink
+ * the valid_passwords array.
+ */
+ for (i = 0; i < passlist->n_members; i++)
+ {
+ HeapTuple tup = &passlist->members[i]->tuple;
+
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_expiration, &isnull);
+
if (!isnull)
vuntil = DatumGetTimestampTz(datum);
- ReleaseSysCache(roleTup);
+ if (isnull || vuntil > current)
+ num_valid_passwords++;
+ }
- /*
- * Password OK, but check to be sure we are not past rolvaliduntil
- */
- if (!isnull && vuntil < GetCurrentTimestamp())
+ passwords = palloc0(sizeof(char *) * num_valid_passwords);
+ *num = num_valid_passwords;
+
+ for (i = 0; i < passlist->n_members; i++)
{
- *logdetail = psprintf(_("User \"%s\" has an expired password."),
- role);
- return NULL;
+ HeapTuple tup = &passlist->members[i]->tuple;
+
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_expiration, &isnull);
+
+ if (!isnull)
+ vuntil = DatumGetTimestampTz(datum);
+
+ if (isnull || vuntil > current)
+ {
+ datum = SysCacheGetAttr(AUTHPASSWORDNAME, tup,
+ Anum_pg_auth_password_password, &isnull);
+ passwords[j++] = pstrdup(TextDatumGetCString(datum));
}
+ }
+
+ ReleaseCatCacheList(passlist);
- return shadow_pass;
+ return passwords;
}
/*
@@ -113,7 +170,7 @@ get_password_type(const char *shadow_pass)
* hash, so it is stored as it is regardless of the requested type.
*/
char *
-encrypt_password(PasswordType target_type, const char *role,
+encrypt_password(PasswordType target_type, const char *salt,
const char *password)
{
PasswordType guessed_type = get_password_type(password);
@@ -134,13 +191,13 @@ encrypt_password(PasswordType target_type, const char *role,
case PASSWORD_TYPE_MD5:
encrypted_password = palloc(MD5_PASSWD_LEN + 1);
- if (!pg_md5_encrypt(password, role, strlen(role),
+ if (!pg_md5_encrypt(password, salt, strlen(salt),
encrypted_password, &errstr))
elog(ERROR, "password encryption failed: %s", errstr);
return encrypted_password;
case PASSWORD_TYPE_SCRAM_SHA_256:
- return pg_be_scram_build_secret(password);
+ return pg_be_scram_build_secret(password, salt);
case PASSWORD_TYPE_PLAINTEXT:
elog(ERROR, "cannot encrypt password with 'plaintext'");
@@ -158,7 +215,7 @@ encrypt_password(PasswordType target_type, const char *role,
* Check MD5 authentication response, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password or password hash, as stored
- * in pg_authid.rolpassword.
+ * in pg_auth_password.password.
* 'client_pass' is the response given by the remote user to the MD5 challenge.
* 'md5_salt' is the salt used in the MD5 authentication challenge.
*
@@ -213,7 +270,7 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
* Check given password for given user, and return STATUS_OK or STATUS_ERROR.
*
* 'shadow_pass' is the user's correct password hash, as stored in
- * pg_authid.rolpassword.
+ * pg_auth_password.password.
* 'client_pass' is the password given by the remote user.
*
* In the error case, store a string at *logdetail that will be sent to the
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7d2032885e..a8f48efcca 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -707,7 +707,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
- EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
+ EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPIRES EXPLAIN EXPRESSION
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR
@@ -743,7 +743,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
- PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD
+ PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSNAME PASSWORD
PLACING PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
@@ -1199,6 +1199,21 @@ AlterOptRoleElem:
errhint("Remove UNENCRYPTED to store the password in encrypted form instead."),
parser_errposition(@1)));
}
+ | PASSNAME Sconst
+ {
+ $$ = makeDefElem("passname",
+ (Node *)makeString($2), @1);
+ }
+ | EXPIRES IN_P Sconst opt_interval
+ {
+ TypeName *t = SystemTypeName("interval");
+ t->typmods = $4;
+ $$ = makeDefElem("expiresin", (Node *)makeStringConstCast($3, @3, t), @1);
+ }
+ | VALID FOR Sconst
+ {
+ $$ = makeDefElem("validFor", (Node *)makeString($3), @1);
+ }
| INHERIT
{
$$ = makeDefElem("inherit", (Node *) makeBoolean(true), @1);
@@ -17110,6 +17125,7 @@ unreserved_keyword:
| EXCLUDING
| EXCLUSIVE
| EXECUTE
+ | EXPIRES
| EXPLAIN
| EXPRESSION
| EXTENSION
@@ -17214,6 +17230,7 @@ unreserved_keyword:
| PARTIAL
| PARTITION
| PASSING
+ | PASSNAME
| PASSWORD
| PLANS
| POLICY
@@ -17670,6 +17687,7 @@ bare_label_keyword:
| EXCLUSIVE
| EXECUTE
| EXISTS
+ | EXPIRES
| EXPLAIN
| EXPRESSION
| EXTENSION
@@ -17815,6 +17833,7 @@ bare_label_keyword:
| PARTIAL
| PARTITION
| PASSING
+ | PASSNAME
| PASSWORD
| PLACING
| PLANS
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 1aacb736c2..71eb0b7f49 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1106,6 +1106,7 @@ IndexScanOK(CatCache *cache, ScanKey cur_skey)
case AUTHNAME:
case AUTHOID:
+ case AUTHPASSWORDNAME:
case AUTHMEMMEMROLE:
case DATABASEOID:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7234cb3da6..710470dd46 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -51,6 +51,7 @@
#include "catalog/pg_attrdef.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
@@ -114,6 +115,7 @@ static const FormData_pg_attribute Desc_pg_proc[Natts_pg_proc] = {Schema_pg_proc
static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type};
static const FormData_pg_attribute Desc_pg_database[Natts_pg_database] = {Schema_pg_database};
static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_authid};
+static const FormData_pg_attribute Desc_pg_auth_password[Natts_pg_auth_password] = {Schema_pg_auth_password};
static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel};
@@ -3495,6 +3497,7 @@ RelationBuildLocalRelation(const char *relname,
{
case DatabaseRelationId:
case AuthIdRelationId:
+ case AuthPasswordRelationId:
case AuthMemRelationId:
case RelationRelationId:
case AttributeRelationId:
@@ -3970,7 +3973,7 @@ RelationCacheInitialize(void)
* RelationCacheInitializePhase2
*
* This is called to prepare for access to shared catalogs during startup.
- * We must at least set up nailed reldescs for pg_database, pg_authid,
+ * We must at least set up nailed reldescs for pg_database, pg_authid, pg_auth_password,
* pg_auth_members, and pg_shseclabel. Ideally we'd like to have reldescs
* for their indexes, too. We attempt to load this information from the
* shared relcache init file. If that's missing or broken, just make
@@ -4011,12 +4014,14 @@ RelationCacheInitializePhase2(void)
Natts_pg_authid, Desc_pg_authid);
formrdesc("pg_auth_members", AuthMemRelation_Rowtype_Id, true,
Natts_pg_auth_members, Desc_pg_auth_members);
+ formrdesc("pg_auth_password", AuthPasswordRelation_Rowtype_Id, true,
+ Natts_pg_auth_password, Desc_pg_auth_password);
formrdesc("pg_shseclabel", SharedSecLabelRelation_Rowtype_Id, true,
Natts_pg_shseclabel, Desc_pg_shseclabel);
formrdesc("pg_subscription", SubscriptionRelation_Rowtype_Id, true,
Natts_pg_subscription, Desc_pg_subscription);
-#define NUM_CRITICAL_SHARED_RELS 5 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS 6 /* fix if you change list above */
}
MemoryContextSwitchTo(oldcxt);
@@ -4153,10 +4158,12 @@ RelationCacheInitializePhase3(void)
AuthIdRelationId);
load_critical_index(AuthMemMemRoleIndexId,
AuthMemRelationId);
+ load_critical_index(AuthPasswordRoleOidIndexId,
+ AuthPasswordRelationId);
load_critical_index(SharedSecLabelObjectIndexId,
SharedSecLabelRelationId);
-#define NUM_CRITICAL_SHARED_INDEXES 6 /* fix if you change list above */
+#define NUM_CRITICAL_SHARED_INDEXES 7 /* fix if you change list above */
criticalSharedRelcachesBuilt = true;
}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 8dbda0024f..f9caf4c1b8 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -27,6 +27,7 @@
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_auth_members.h"
+#include "catalog/pg_auth_password.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
@@ -213,6 +214,13 @@ static const struct cachedesc cacheinfo[] = {
KEY(Anum_pg_authid_oid),
8
},
+ [AUTHPASSWORDNAME] = {
+ AuthPasswordRelationId,
+ AuthPasswordRoleOidIndexId,
+ KEY(Anum_pg_auth_password_roleid,
+ Anum_pg_auth_password_name),
+ 8
+ },
[CASTSOURCETARGET] = {
CastRelationId,
CastSourceTargetIndexId,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index bdb26e2b77..4a5c3866d8 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -610,6 +610,8 @@ static char *recovery_target_xid_string;
static char *recovery_target_name_string;
static char *recovery_target_lsn_string;
+static char *password_duration_string;
+
/* should be static, but commands/variable.c needs to get at this */
char *role_string;
@@ -4540,6 +4542,16 @@ struct config_string ConfigureNamesString[] =
"",
check_debug_io_direct, assign_debug_io_direct, NULL
},
+ {
+ {"password_valid_duration", PGC_SUSET, CONN_AUTH_AUTH,
+ gettext_noop("Specifies the default validity duration of new passwords."),
+ NULL,
+ GUC_SUPERUSER_ONLY | GUC_NOT_IN_SAMPLE
+ },
+ &password_duration_string,
+ NULL,
+ check_password_duration, assign_password_duration, show_password_duration
+ },
/* End-of-list marker */
{
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index bddb30d766..3c3b874580 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1567,10 +1567,10 @@ static void
setup_auth(FILE *cmdfd)
{
/*
- * The authid table shouldn't be readable except through views, to ensure
+ * The pg_auth_password table shouldn't be readable except through views, to ensure
* passwords are not publicly visible.
*/
- PG_CMD_PUTS("REVOKE ALL ON pg_authid FROM public;\n\n");
+ PG_CMD_PUTS("REVOKE ALL ON pg_auth_password FROM public;\n\n");
if (superuser_password)
PG_CMD_PRINTF("ALTER USER \"%s\" WITH PASSWORD E'%s';\n\n",
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index e2a9733d34..bfa15e9696 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -762,7 +762,18 @@ dumpRoles(PGconn *conn)
int i;
/* note: rolconfig is dumped later */
- if (server_version >= 90600)
+ if (server_version >= 170000)
+ printfPQExpBuffer(buf,
+ "SELECT oid, rolname, rolsuper, rolinherit, "
+ "rolcreaterole, rolcreatedb, "
+ "rolcanlogin, rolconnlimit, p.password as rolpassword, "
+ "rolvaliduntil, rolreplication, rolbypassrls, "
+ "pg_catalog.shobj_description(oid, '%s') as rolcomment, "
+ "rolname = current_user AS is_current_user "
+ "FROM %s LEFT JOIN pg_auth_password p ON %s.oid = p.roleid "
+ "WHERE rolname !~ '^pg_' "
+ "ORDER BY 2", role_catalog, role_catalog, role_catalog);
+ else if (server_version >= 90600)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index ef997ef684..df48f74f88 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -185,7 +185,7 @@ scram_ServerKey(const uint8 *salted_password,
/*
- * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
+ * Construct a SCRAM secret, for storing in pg_auth_password.password.
*
* The password should already have been processed with SASLprep, if necessary!
*
diff --git a/src/include/catalog/pg_auth_password.h b/src/include/catalog/pg_auth_password.h
new file mode 100644
index 0000000000..a1e0741d63
--- /dev/null
+++ b/src/include/catalog/pg_auth_password.h
@@ -0,0 +1,50 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_auth_password.h
+ * definition of the "authorization identifier" system catalog (pg_auth_password)
+ *
+ * Portions Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/pg_auth_password.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_AUTH_PASSWORD_H
+#define PG_AUTH_PASSWORD_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_auth_password_d.h"
+
+/* ----------------
+ * pg_auth_password definition. cpp turns this into
+ * typedef struct FormData_pg_auth_password
+ * ----------------
+ */
+CATALOG(pg_auth_password,4551,AuthPasswordRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(4552,AuthPasswordRelation_Rowtype_Id) BKI_SCHEMA_MACRO
+{
+ Oid roleid BKI_LOOKUP(pg_authid); /* ID of a role */
+ NameData name; /* name of password for multiple password support */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text password BKI_FORCE_NOT_NULL; /* password */
+ timestamptz expiration BKI_FORCE_NULL; /* password expiration time, if any */
+#endif
+} FormData_pg_auth_password;
+
+/* ----------------
+ * Form_pg_auth_password corresponds to a pointer to a tuple with
+ * the format of pg_auth_password relation.
+ * ----------------
+ */
+
+typedef FormData_pg_auth_password *Form_pg_auth_password;
+
+DECLARE_TOAST_WITH_MACRO(pg_auth_password, 9122, 9133, PgAuthPasswordToastTable, PgAuthPasswordToastIndex);
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_auth_password_roleoid_index, 9134, AuthPasswordRoleOidIndexId, pg_auth_password, btree(roleid oid_ops, name name_ops));
+
+#endif /* PG_AUTH_PASSWORD_H */
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 6b4a0aaaad..1dbf5ca0e0 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -23,76 +23,76 @@
rolname => 'POSTGRES', rolsuper => 't', rolinherit => 't',
rolcreaterole => 't', rolcreatedb => 't', rolcanlogin => 't',
rolreplication => 't', rolbypassrls => 't', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6171', oid_symbol => 'ROLE_PG_DATABASE_OWNER',
rolname => 'pg_database_owner', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6181', oid_symbol => 'ROLE_PG_READ_ALL_DATA',
rolname => 'pg_read_all_data', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6182', oid_symbol => 'ROLE_PG_WRITE_ALL_DATA',
rolname => 'pg_write_all_data', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3373', oid_symbol => 'ROLE_PG_MONITOR',
rolname => 'pg_monitor', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3374', oid_symbol => 'ROLE_PG_READ_ALL_SETTINGS',
rolname => 'pg_read_all_settings', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3375', oid_symbol => 'ROLE_PG_READ_ALL_STATS',
rolname => 'pg_read_all_stats', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '3377', oid_symbol => 'ROLE_PG_STAT_SCAN_TABLES',
rolname => 'pg_stat_scan_tables', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4569', oid_symbol => 'ROLE_PG_READ_SERVER_FILES',
rolname => 'pg_read_server_files', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4570', oid_symbol => 'ROLE_PG_WRITE_SERVER_FILES',
rolname => 'pg_write_server_files', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4571', oid_symbol => 'ROLE_PG_EXECUTE_SERVER_PROGRAM',
rolname => 'pg_execute_server_program', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4200', oid_symbol => 'ROLE_PG_SIGNAL_BACKEND',
rolname => 'pg_signal_backend', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4544', oid_symbol => 'ROLE_PG_CHECKPOINT',
rolname => 'pg_checkpoint', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '4550', oid_symbol => 'ROLE_PG_USE_RESERVED_CONNECTIONS',
rolname => 'pg_use_reserved_connections', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
{ oid => '6304', oid_symbol => 'ROLE_PG_CREATE_SUBSCRIPTION',
rolname => 'pg_create_subscription', rolsuper => 'f', rolinherit => 't',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
- rolpassword => '_null_', rolvaliduntil => '_null_' },
+ rolvaliduntil => '_null_' },
]
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index 0e7ddc56ea..56190512cf 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -42,9 +42,8 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
int32 rolconnlimit; /* max connections allowed (-1=no limit) */
/* remaining fields may be null; use heap_getattr to read them! */
-#ifdef CATALOG_VARLEN /* variable-length fields start here */
- text rolpassword; /* password, if any */
- timestamptz rolvaliduntil; /* password expiration time, if any */
+#ifdef CATALOG_VARLEN
+ timestamptz rolvaliduntil BKI_FORCE_NULL; /* role expiration time, if any */
#endif
} FormData_pg_authid;
@@ -55,8 +54,6 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
*/
typedef FormData_pg_authid *Form_pg_authid;
-DECLARE_TOAST_WITH_MACRO(pg_authid, 4175, 4176, PgAuthidToastTable, PgAuthidToastIndex);
-
DECLARE_UNIQUE_INDEX(pg_authid_rolname_index, 2676, AuthIdRolnameIndexId, pg_authid, btree(rolname name_ops));
DECLARE_UNIQUE_INDEX_PKEY(pg_authid_oid_index, 2677, AuthIdOidIndexId, pg_authid, btree(oid oid_ops));
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 97dcb93791..75bb58eea8 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -21,6 +21,9 @@
extern PGDLLIMPORT int Password_encryption; /* values from enum PasswordType */
extern PGDLLIMPORT char *createrole_self_grant;
+/* GUC. system-wide password validity duration */
+extern Interval *default_password_duration;
+
/* Hook to check passwords in CreateRole() and AlterRole() */
typedef void (*check_password_hook_type) (const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null);
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index ddcd27469a..2b66fd909a 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -21,7 +21,7 @@
* Plaintext passwords can be passed in by the user, in a CREATE/ALTER USER
* command. They will be encrypted to MD5 or SCRAM-SHA-256 format, before
* storing on-disk, so only MD5 and SCRAM-SHA-256 passwords should appear
- * in pg_authid.rolpassword. They are also the allowed values for the
+ * in pg_auth_password.password. They are also the allowed values for the
* password_encryption GUC.
*/
typedef enum PasswordType
@@ -35,7 +35,7 @@ extern PasswordType get_password_type(const char *shadow_pass);
extern char *encrypt_password(PasswordType target_type, const char *role,
const char *password);
-extern char *get_role_password(const char *role, const char **logdetail);
+extern char **get_role_passwords(const char *role, const char **logdetail, int *num);
extern int md5_crypt_verify(const char *role, const char *shadow_pass,
const char *client_pass, const char *md5_salt,
diff --git a/src/include/libpq/sasl.h b/src/include/libpq/sasl.h
index 7a1b1ed0a0..12c5c9602b 100644
--- a/src/include/libpq/sasl.h
+++ b/src/include/libpq/sasl.h
@@ -77,7 +77,7 @@ typedef struct pg_be_sasl_mech
* disclosing valid user names.
*---------
*/
- void *(*init) (Port *port, const char *mech, const char *shadow_pass);
+ void *(*init) (Port *port, const char *mech, const char **secrets, const int num_secrets);
/*---------
* exchange()
@@ -131,6 +131,6 @@ typedef struct pg_be_sasl_mech
/* Common implementation for auth.c */
extern int CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port,
- char *shadow_pass, const char **logdetail);
+ const char **passwords, int num_passwords, const char **logdetail);
#endif /* PG_SASL_H */
diff --git a/src/include/libpq/scram.h b/src/include/libpq/scram.h
index 310bc36517..07d9cc3990 100644
--- a/src/include/libpq/scram.h
+++ b/src/include/libpq/scram.h
@@ -25,7 +25,7 @@ extern PGDLLIMPORT int scram_sha_256_iterations;
extern PGDLLIMPORT const pg_be_sasl_mech pg_be_scram_mech;
/* Routines to handle and check SCRAM-SHA-256 secret */
-extern char *pg_be_scram_build_secret(const char *password);
+extern char *pg_be_scram_build_secret(const char *password, const char *salt);
extern bool parse_scram_secret(const char *secret,
int *iterations,
pg_cryptohash_type *hash_type,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 5984dcfa4b..af0bba6244 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -160,6 +160,7 @@ PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("expires", EXPIRES, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("expression", EXPRESSION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -325,6 +326,7 @@ PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("passname", PASSNAME, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index f04b99e3b9..554c3c06b3 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -161,4 +161,8 @@ extern void assign_wal_consistency_checking(const char *newval, void *extra);
extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
extern void assign_xlog_sync_method(int new_sync_method, void *extra);
+extern bool check_password_duration(char **newval, void **extra, GucSource source);
+extern void assign_password_duration(const char *newval, void *extra);
+extern const char *show_password_duration(void);
+
#endif /* GUC_HOOKS_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 67ea6e4945..6015f53d73 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -43,6 +43,7 @@ enum SysCacheIdentifier
AUTHMEMROLEMEM,
AUTHNAME,
AUTHOID,
+ AUTHPASSWORDNAME,
CASTSOURCETARGET,
CLAAMNAMENSP,
CLAOID,
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index acfd9d1f4f..3ba23628a1 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2521,10 +2521,10 @@ REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
ERROR: cannot reindex system catalogs concurrently
REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
ERROR: cannot reindex system catalogs concurrently
--- These are the toast table and index of pg_authid.
-REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
+-- These are the toast table and index of pg_database.
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1262; -- no catalog toast table
ERROR: cannot reindex system catalogs concurrently
-REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -- no catalog toast index
ERROR: cannot reindex system catalogs concurrently
REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
ERROR: cannot reindex system catalogs concurrently
@@ -2830,10 +2830,10 @@ ERROR: must be owner of schema schema_to_reindex
RESET ROLE;
GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
SET SESSION ROLE regress_reindexuser;
-REINDEX TABLE pg_toast.pg_toast_1260;
-ERROR: must be owner of table pg_toast_1260
-REINDEX INDEX pg_toast.pg_toast_1260_index;
-ERROR: must be owner of index pg_toast_1260_index
+REINDEX TABLE pg_toast.pg_toast_1262;
+ERROR: must be owner of table pg_toast_1262
+REINDEX INDEX pg_toast.pg_toast_1262_index;
+ERROR: must be owner of index pg_toast_1262_index
-- Clean up
RESET ROLE;
REVOKE USAGE ON SCHEMA pg_toast FROM regress_reindexuser;
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be..b69a2a72a7 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -197,6 +197,7 @@ NOTICE: checking pg_tablespace {spcowner} => pg_authid {oid}
NOTICE: checking pg_auth_members {roleid} => pg_authid {oid}
NOTICE: checking pg_auth_members {member} => pg_authid {oid}
NOTICE: checking pg_auth_members {grantor} => pg_authid {oid}
+NOTICE: checking pg_auth_password {roleid} => pg_authid {oid}
NOTICE: checking pg_shdepend {dbid} => pg_database {oid}
NOTICE: checking pg_shdepend {classid} => pg_class {oid}
NOTICE: checking pg_shdepend {refclassid} => pg_class {oid}
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 8475231735..0d44ac3915 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -24,10 +24,12 @@ CREATE ROLE regress_passwd4 PASSWORD NULL;
--
-- Since the salt is random, the exact value stored will be different on every test
-- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
rolname | rolpassword_masked
-----------------+---------------------------------------------------
regress_passwd1 | md5783277baca28003b33453252be4dbb34
@@ -40,12 +42,14 @@ SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+
ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new;
NOTICE: MD5 password cleared because of role rename
-- md5 entry should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd2_new'
- ORDER BY rolname, rolpassword;
- rolname | rolpassword
----------------------+-------------
+ ORDER BY rolname, password;
+ rolname | password
+---------------------+----------
regress_passwd2_new |
(1 row)
@@ -75,10 +79,12 @@ CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
-- Changing the SCRAM iteration count
SET scram_iterations = 1024;
CREATE ROLE regress_passwd9 PASSWORD 'alterediterationcount';
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
rolname | rolpassword_masked
-----------------+---------------------------------------------------
regress_passwd1 | md5cd3578025fe2c3d7ed1b9a9b26238b70
@@ -99,9 +105,11 @@ ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
NOTICE: empty string is not a valid password, clearing password
ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
NOTICE: empty string is not a valid password, clearing password
-SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
- rolpassword
--------------
+SELECT password FROM pg_authid
+LEFT JOIN pg_auth_password p
+ON pg_authid.oid = p.roleid WHERE rolname='regress_passwd_empty';
+ password
+----------
(1 row)
@@ -114,8 +122,10 @@ CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';
-- Check that the invalid secrets were re-hashed. A re-hashed secret
-- should not contain the original salt.
-SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
+SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
rolname | is_rolpassword_rehashed
@@ -139,11 +149,13 @@ DROP ROLE regress_passwd_sha_len0;
DROP ROLE regress_passwd_sha_len1;
DROP ROLE regress_passwd_sha_len2;
-- all entries should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
- rolname | rolpassword
----------+-------------
+ ORDER BY rolname, password;
+ rolname | password
+---------+----------
(0 rows)
diff --git a/src/test/regress/expected/roleattributes.out b/src/test/regress/expected/roleattributes.out
index 5e6969b173..18a9dacadd 100644
--- a/src/test/regress/expected/roleattributes.out
+++ b/src/test/regress/expected/roleattributes.out
@@ -1,233 +1,233 @@
-- default for superuser is false
CREATE ROLE regress_test_def_superuser;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_superuser | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_superuser | t | t | f | f | f | f | f | -1 |
(1 row)
-- default for inherit is true
CREATE ROLE regress_test_def_inherit;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_inherit | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_inherit WITH INHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_inherit | f | f | f | f | f | f | f | -1 |
(1 row)
-- default for create role is false
CREATE ROLE regress_test_def_createrole;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_createrole | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createrole | f | t | t | f | f | f | f | -1 |
(1 row)
-- default for create database is false
CREATE ROLE regress_test_def_createdb;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_createdb | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createdb WITH NOCREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_createdb | f | t | f | t | f | f | f | -1 |
(1 row)
-- default for can login is false for role
CREATE ROLE regress_test_def_role_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_role_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_role_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
-- default for can login is true for user
CREATE USER regress_test_def_user_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_user_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
CREATE USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER USER regress_test_user_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | t | f | f | -1 |
(1 row)
ALTER USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_user_canlogin | f | t | f | f | f | f | f | -1 |
(1 row)
-- default for replication is false
CREATE ROLE regress_test_def_replication;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_replication | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_replication | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | t | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
ALTER ROLE regress_test_replication WITH NOREPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | t | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_replication | f | t | f | f | f | t | f | -1 |
(1 row)
-- default for bypassrls is false
CREATE ROLE regress_test_def_bypassrls;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_bypassrls | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_def_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | f | -1 |
(1 row)
ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolvaliduntil
+------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+---------------
+ regress_test_bypassrls | f | t | f | f | f | f | t | -1 |
(1 row)
-- clean up roles
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 5058be5411..ddbf56b940 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1727,11 +1727,12 @@ pg_shadow| SELECT pg_authid.rolname AS usename,
pg_authid.rolsuper AS usesuper,
pg_authid.rolreplication AS userepl,
pg_authid.rolbypassrls AS usebypassrls,
- pg_authid.rolpassword AS passwd,
+ p.password AS passwd,
pg_authid.rolvaliduntil AS valuntil,
s.setconfig AS useconfig
- FROM (pg_authid
+ FROM ((pg_authid
LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid))))
+ LEFT JOIN pg_auth_password p ON (p.roleid = pg_authid.oid))
WHERE pg_authid.rolcanlogin;
pg_shmem_allocations| SELECT name,
off,
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 9aabb85349..dd535d41a3 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -51,13 +51,13 @@ ERROR: cannot move system relation "pg_authid_rolname_index"
REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
ERROR: cannot reindex system catalogs concurrently
-- toast relations, fail
-REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
-ERROR: cannot move system relation "pg_toast_1260_index"
-REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1260_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index;
+ERROR: cannot move system relation "pg_toast_1262_index"
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1262_index;
ERROR: cannot reindex system catalogs concurrently
-REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
-ERROR: cannot move system relation "pg_toast_1260_index"
-REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262;
+ERROR: cannot move system relation "pg_toast_1262_index"
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1262;
ERROR: cannot reindex system catalogs concurrently
-- system catalog, fail
REINDEX (TABLESPACE pg_global) TABLE pg_authid;
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index d49ce9f300..d2c63e424c 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -1074,9 +1074,9 @@ REINDEX TABLE CONCURRENTLY concur_reindex_tab;
COMMIT;
REINDEX TABLE CONCURRENTLY pg_class; -- no catalog relation
REINDEX INDEX CONCURRENTLY pg_class_oid_index; -- no catalog index
--- These are the toast table and index of pg_authid.
-REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1260; -- no catalog toast table
-REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1260_index; -- no catalog toast index
+-- These are the toast table and index of pg_database.
+REINDEX TABLE CONCURRENTLY pg_toast.pg_toast_1262; -- no catalog toast table
+REINDEX INDEX CONCURRENTLY pg_toast.pg_toast_1262_index; -- no catalog toast index
REINDEX SYSTEM CONCURRENTLY postgres; -- not allowed for SYSTEM
REINDEX (CONCURRENTLY) SYSTEM postgres; -- ditto
REINDEX (CONCURRENTLY) SYSTEM; -- ditto
@@ -1253,8 +1253,8 @@ REINDEX SCHEMA schema_to_reindex;
RESET ROLE;
GRANT USAGE ON SCHEMA pg_toast TO regress_reindexuser;
SET SESSION ROLE regress_reindexuser;
-REINDEX TABLE pg_toast.pg_toast_1260;
-REINDEX INDEX pg_toast.pg_toast_1260_index;
+REINDEX TABLE pg_toast.pg_toast_1262;
+REINDEX INDEX pg_toast.pg_toast_1262_index;
-- Clean up
RESET ROLE;
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 53e86b0b6c..b4b6ab9847 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -23,18 +23,22 @@ CREATE ROLE regress_passwd4 PASSWORD NULL;
--
-- Since the salt is random, the exact value stored will be different on every test
-- run. Use a regular expression to mask the changing parts.
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
-- Rename a role
ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new;
-- md5 entry should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd2_new'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
ALTER ROLE regress_passwd2_new RENAME TO regress_passwd2;
-- Change passwords with ALTER USER. With plaintext or already-encrypted
@@ -67,16 +71,20 @@ CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz';
SET scram_iterations = 1024;
CREATE ROLE regress_passwd9 PASSWORD 'alterediterationcount';
-SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
+SELECT rolname, regexp_replace(password, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:<salt>$<storedkey>:<serverkey>') as rolpassword_masked
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
-- An empty password is not allowed, in any form
CREATE ROLE regress_passwd_empty PASSWORD '';
ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a';
ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4=';
-SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty';
+SELECT password FROM pg_authid
+LEFT JOIN pg_auth_password p
+ON pg_authid.oid = p.roleid WHERE rolname='regress_passwd_empty';
-- Test with invalid stored and server keys.
--
@@ -88,8 +96,10 @@ CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941
-- Check that the invalid secrets were re-hashed. A re-hashed secret
-- should not contain the original salt.
-SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
+SELECT rolname, password not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd_sha_len%'
ORDER BY rolname;
@@ -108,7 +118,9 @@ DROP ROLE regress_passwd_sha_len1;
DROP ROLE regress_passwd_sha_len2;
-- all entries should have been removed
-SELECT rolname, rolpassword
+SELECT rolname, password
FROM pg_authid
+ LEFT JOIN pg_auth_password p
+ ON pg_authid.oid = p.roleid
WHERE rolname LIKE 'regress_passwd%'
- ORDER BY rolname, rolpassword;
+ ORDER BY rolname, password;
diff --git a/src/test/regress/sql/roleattributes.sql b/src/test/regress/sql/roleattributes.sql
index c961b2d730..09505c6a3b 100644
--- a/src/test/regress/sql/roleattributes.sql
+++ b/src/test/regress/sql/roleattributes.sql
@@ -1,83 +1,83 @@
-- default for superuser is false
CREATE ROLE regress_test_def_superuser;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
CREATE ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
ALTER ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
-- default for inherit is true
CREATE ROLE regress_test_def_inherit;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
CREATE ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
ALTER ROLE regress_test_inherit WITH INHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
ALTER ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
-- default for create role is false
CREATE ROLE regress_test_def_createrole;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
CREATE ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
ALTER ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
-- default for create database is false
CREATE ROLE regress_test_def_createdb;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
CREATE ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
ALTER ROLE regress_test_createdb WITH NOCREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
ALTER ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
-- default for can login is false for role
CREATE ROLE regress_test_def_role_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
CREATE ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
ALTER ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
-- default for can login is true for user
CREATE USER regress_test_def_user_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
CREATE USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
ALTER USER regress_test_user_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
ALTER USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
-- default for replication is false
CREATE ROLE regress_test_def_replication;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
CREATE ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
ALTER ROLE regress_test_replication WITH NOREPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
ALTER ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
-- default for bypassrls is false
CREATE ROLE regress_test_def_bypassrls;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
-- clean up roles
DROP ROLE regress_test_def_superuser;
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index d274d9615e..c8b83788f0 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -40,10 +40,10 @@ REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_am;
REINDEX (TABLESPACE regress_tblspace) TABLE pg_authid;
REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_authid;
-- toast relations, fail
-REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1260_index;
-REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1260_index;
-REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1260;
-REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1260;
+REINDEX (TABLESPACE regress_tblspace) INDEX pg_toast.pg_toast_1262_index;
+REINDEX (TABLESPACE regress_tblspace) INDEX CONCURRENTLY pg_toast.pg_toast_1262_index;
+REINDEX (TABLESPACE regress_tblspace) TABLE pg_toast.pg_toast_1262;
+REINDEX (TABLESPACE regress_tblspace) TABLE CONCURRENTLY pg_toast.pg_toast_1262;
-- system catalog, fail
REINDEX (TABLESPACE pg_global) TABLE pg_authid;
REINDEX (TABLESPACE pg_global) TABLE CONCURRENTLY pg_authid;
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2023-09-26 23:36 Jeff Davis <[email protected]>
parent: Gurjeet Singh <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Jeff Davis @ 2023-09-26 23:36 UTC (permalink / raw)
To: Gurjeet Singh <[email protected]>; Stephen Frost <[email protected]>; +Cc: Brindle, Joshua <[email protected]>; Jacob Champion <[email protected]>; pgsql-hackers; Nathan Bossart <[email protected]>
On Mon, 2023-09-25 at 00:31 -0700, Gurjeet Singh wrote:
> Please see attached v4 of the patch. The patch takes care of rebase
> to
> the master/17-devel branch, and includes some changes, too.
FWIW I got some failures applying. I didn't investigate much, and
instead I looked at your git branch (7a35619e).
> Moreover, before the patch, in case of CheckPasswordAuth(), the error
> (if any) would have been thrown _after_ network communication done by
> sendAuthRequest() call. But with this patch, the error is thrown
> before the network interaction, hence this changes the order of
> network interaction and the error message. This may have security
> implications, too, but I'm unable to articulate one right now.
You mean before v3 or before v4? Is this currently a problem in v4?
> Open question: If a client is capable of providing just md5 passwords
> handshake, and because of pg_hba.conf setting, or because the role
> has
> at least one SCRAM password (essentially the 3rd case you mention
> above: pg_hba md5 + md5 and scram pws -> scram), the server will
> respond with a SASL/SCRAM authentication response, and that would
> break the backwards compatibility and will deny access to the client.
> Does this make it necessary to use a newer libpq/client library?
Perhaps you can try the MD5 passwords first, and only if they fail,
move on to try scram passwords?
> Comments?
IIUC, for the case of multiple scram passwords, we use the salt to
select the right scram password, and then proceed from there?
I'm not very excited about the idea of naming passwords, or having
passwords with default names. I can't think of anything better right
now, so it might be OK.
> - Add tests
> - Add/update documentation
These are needed to provide better review.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v9 5/7] Row pattern recognition patch (docs).
@ 2023-10-04 05:51 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-10-04 05:51 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 52 ++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 +++++++++++++++++++++++--
3 files changed, 142 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..f39ec8f2d5 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,58 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used with row pattern common syntax to
+ perform row pattern recognition in a query. Row pattern common syntax
+ includes two sub clauses. <literal>DEFINE</literal> defines definition
+ variables along with an expression. The expression must be a logical
+ expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. Moreover if the expression comprises a column
+ reference, it must be the argument of <function>rpr</function>. An example
+ of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns price column in the previous
+ row if it's called in a context of row pattern recognition. So in the
+ second line means the definition variable "UP" is <literal>TRUE</literal>
+ when price column in the current row is greater than the price column in
+ the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". If a
+ sequence of rows found, rpr returns the column at the starting row.
+ Example of a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price, max(price) OVER w FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f1ad64c3d6..6c46ea4355 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21780,6 +21780,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21819,6 +21820,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 42d78913cf..522ad9dd70 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Wed_Oct__4_15_03_28_2023_821)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v9-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2023-10-05 05:41 Gurjeet Singh <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Gurjeet Singh @ 2023-10-05 05:41 UTC (permalink / raw)
To: PostgreSQL Hackers <[email protected]>; +Cc: Stephen Frost <[email protected]>; Brindle, Joshua <[email protected]>; Jacob Champion <[email protected]>; Nathan Bossart <[email protected]>; Jeff Davis <[email protected]>
I had an idea to simplify this feature/patch and after some validation
in internal discussions, I am posting the new approach here. I'd
appreciate any feedback and comments.
To begin with, the feature we are chasing is to make it convenient for
the users to rollover their passwords. Currently there is no easy way
to rollover passwords without increasing the risk of an application
outage. After a password change, the users/admins have to rush to
change the password in all locations where it is stored. There is a
window of time where if the application password is not changed to the
new one, and the application tries to connect/reconnect for any
reason, the application will fail authentication and lead to an outage.
I personally haven't seen any attempts by any
application/driver/framework to solve this problem in the wild, so
following is just me theorizing how one may solve this problem on the
application side; there may be other ways in which others may solve
this problem. The application may be written in such a way that upon
password authentication failure, it tries again with a second
password. The application's configuration file (or environment
variables) may allow specifying 2 passwords at the same time, and the
application will keep trying these 2 passwords alternatively until it
succeeds or the user restarts it with a new configuration. With such a
logic in place in their application, the users may first change the
configuration of all the instances of the application to hold the new
password along with the old/current working password, and only then
change the password in the database. This way, in the event of an
application instance start/restart either the old password will
succeed, or the new password will.
There may be other ways to solve this problem, but I can't imagine any
of those ways to be convenient and straightforward. At least not as
convenient as it can be if the database itself allowed for storing
both the passwords, and honored both passwords at the same time, while
allowing to associate a separate validity period with each of the
passwords.
The patches posted in this thread so far attempt to add the ability to
allow the user to have an arbitrary number of passwords. I believe
that allowing arbitrary number of passwords is not only unnecessary,
but the need to name passwords, the need to store them in a shared
catalog, etc. may actually create problems in the field. The
users/admins will have to choose names for passwords, which they
didn't have to previously. The need to name them may also lead to
users storing password-hints in the password names (e.g. 'mom''s
birthday', 'ex''s phone number', 'third password'), rendering the
passwords weak.
Moreover, allowing an arbitrarily many number of passwords will
require us to provide additional infrastructure to solve problems like
observability (which passwords are currently in use, and which ones
have been effectively forgotten by applications), or create a nuisance
for admins that can create more problems than it solves.
So I propose that the feature should allow no more than 2 passwords
for a role, each with their own validity periods. This eliminates the
need to name passwords, because at any given time there are no more
than 2 passwords; current one, and old one. This also eliminates the
need for a shared catalog to hold passwords, because with the limit of
2 imposed, we can store the old password and its validity period in
additional columns in the pg_authid table.
The patches so far also add a notion of max validity period of
passwords, which only a superuser can override. I believe this is a
useful feature, but that feature can be dealt with separately,
independent of password rollover feature. So in the newer patches I
will not include the relevant GUC and code.
With the above being said, following is the user interface I can think
of that can allow for various operations that users may need to
perform to rollover their passwords. The 'ADD PASSWORD' and 'ALL
PASSWORD' are additions to the grammar. rololdpassword and
rololdvaliduntil will be new columns in pg_authid that will hold the
old password and its valid-until value.
In essence, we create a stack that can hold 2 passwords. Pushing an
element when it's full will make it forget the bottom element. Popping
the stack makes it forget the top element, and the only remaining
element, if any, becomes the top.
-- Create a user, as usual
CREATE ROLE u1 PASSWORD 'p1' VALID UNTIL '2020/01/01';
-- Add another password that the user can use for authentication. This moves
-- the 'p1' password hash and its valid-until value to rololdpassword and
-- rololdvaliduntil, respectively.
ALTER ROLE u1 ADD PASSWORD 'p2' VALID UNTIL '2021/01/01';
-- Change the password 'p2's (current password's) validity
ALTER ROLE u1 VALID UNTIL '2022/01/01';
-- Note that currently I don't have a proposal for how to change the old
-- password's validity period, without deleting the latest/main password. See
-- PASSWORD NULL command below on how to delete the current password. It's very
-- likely that in a password rollover use case it's unnecessary, even
-- undesirable, to change the old password's validity period.
-- If, for some reason, the user wants to get rid of the latest password added.
-- Remove 'p2' (current password). The old password (p1), will be restored to
-- rolpassword, along with its valid-until value.
ALTER ROLE u1 PASSWORD NULL;
-- This may come as a surprise to some users, because currently they expect the
-- user to completely lose the ability to use passwords for login after this
-- command. To get the old behavior, the user must now use the ALL PASSWORD
-- NULL incantation; see below.
-- Issuing this command one more time will remove even the restored password,
-- hence leaving the user with no passwords.
-- Change the validity of the restored/current password (p1)
ALTER ROLE u1 VALID UNTIL '2022/01/01';
-- Add a new password (p3) without affecting old password's (p1) validity
ALTER ROLE u1 ADD PASSWORD 'p3' VALID UNTIL '2023/01/01';
-- Add a new password 'p4'. This will move 'p3' to rololdpassword, and hence
-- 'p1' will be forgotten completely.
-- After this command, user can use passwords 'p3' (old) and 'p4' (current) to
-- login.
ALTER ROLE u1 ADD PASSWORD 'p4' VALID UNTIL '2024/01/01';
-- Replace 'p4' (current) password with 'p5'. Note that this command is _not_
-- using the ADD keyword, hence 'p4' is _not_ moved to rololdpassword column.
-- After this command, user can use passwords 'p3' (old) and 'p5'
-- (current) to login.
ALTER ROLE u1 PASSWORD 'p5' VALID UNTIL '2025/01/01';
-- Using the old password to login will produce a warning, hopefully nudging
-- the user to start using the new password.
export PGPASSWORD=p3
psql -U u1
...
WARNING: Used old password to login
export PGPASSWORD=p5
psql -U u1
...
=> (no warning)
-- Remove all passwords from the role. Even old password, if any, is removed.
ALTER ROLE u1 ALL PASSWORD NULL;
In normal use, the users can simply keep ADDing new passwords, and the
database will promptly remember only one old password, and keep
forgetting any passwords older than that. But on the off chance
that someone needs to forget the latest password they added, and
restore the old password to be the "current" password, they can use
the PASSWORD NULL incantation. Note that this will result in
rol*old*password being set to NULL, because our password memory stack
cannot hold more than 2 elements.
Since this feature is targeted towards password rollovers, it's a legitimate
question to ask if we should enforce that the new password being added has a
valid-until greater than the valid-until of the existing/old password. I don't
think we should enforce this, at least not in this patch, because the
user/admin may have a use case where they want a short-lived new password that
they intend/want to change very soon; I'm thinking of cases where passwords are
being rolled over while they are also moving from older clients/libraries that
don't yet support scram-sha-256; keep using md5 and add passwords to honor
password rollover policy, but then as soon as all clients have been updated and
have the ability to use scram-sha-256, rollover the password again to utilize
the better mechanism.
I realize that allowing for a maximum of 2 passwords goes against the
zero-one-infinity rule [1], but I think in the case of password
rollovers it's perfectly acceptable to limit the number of active
passwords to just 2. If there are use cases, either related to password
rollovers, or in its vicinity, that can be better addressed by
allowing an arbitrarily many passwords, I would love to learn about
them and change this design to accommodate for those use cases, or
perhaps revert to pursuing the multiple-passwords feature.
[1]: https://en.wikipedia.org/wiki/Zero_one_infinity_rule
Best regards,
Gurjeet
http://Gurje.et
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2023-10-05 19:04 Nathan Bossart <[email protected]>
parent: Gurjeet Singh <[email protected]>
0 siblings, 1 reply; 36+ messages in thread
From: Nathan Bossart @ 2023-10-05 19:04 UTC (permalink / raw)
To: Gurjeet Singh <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; Stephen Frost <[email protected]>; Brindle, Joshua <[email protected]>; Jacob Champion <[email protected]>; Jeff Davis <[email protected]>
On Wed, Oct 04, 2023 at 10:41:15PM -0700, Gurjeet Singh wrote:
> The patches posted in this thread so far attempt to add the ability to
> allow the user to have an arbitrary number of passwords. I believe
> that allowing arbitrary number of passwords is not only unnecessary,
> but the need to name passwords, the need to store them in a shared
> catalog, etc. may actually create problems in the field. The
> users/admins will have to choose names for passwords, which they
> didn't have to previously. The need to name them may also lead to
> users storing password-hints in the password names (e.g. 'mom''s
> birthday', 'ex''s phone number', 'third password'), rendering the
> passwords weak.
>
> Moreover, allowing an arbitrarily many number of passwords will
> require us to provide additional infrastructure to solve problems like
> observability (which passwords are currently in use, and which ones
> have been effectively forgotten by applications), or create a nuisance
> for admins that can create more problems than it solves.
IMHO neither of these problems seems insurmountable. Besides advising
against using hints as names, we could also automatically generate safe
names, or even disallow user-provided names entirely. And adding
observability for passwords seems worthwhile anyway.
> So I propose that the feature should allow no more than 2 passwords
> for a role, each with their own validity periods. This eliminates the
> need to name passwords, because at any given time there are no more
> than 2 passwords; current one, and old one. This also eliminates the
> need for a shared catalog to hold passwords, because with the limit of
> 2 imposed, we can store the old password and its validity period in
> additional columns in the pg_authid table.
Another approach could be to allow an abritrary number of passwords but to
also allow administrators to limit how many passwords can be associated to
each role. That way, we needn't restrict this feature to 2 passwords for
everyone. Perhaps 2 should be the default, but in any case, IMO we
shouldn't design to only support 2.
> In essence, we create a stack that can hold 2 passwords. Pushing an
> element when it's full will make it forget the bottom element. Popping
> the stack makes it forget the top element, and the only remaining
> element, if any, becomes the top.
I think this would be mighty confusing to users since it's not clear that
adding a password will potentially invalidate a current password (which
might be actively in use), but only if there are already 2 in the stack. I
worry that such a desіgn might be too closely tailored to the
implementation details. If we proceed with this design, perhaps we should
consider ERROR-ing if a user tries to add a third password.
> -- If, for some reason, the user wants to get rid of the latest password added.
> -- Remove 'p2' (current password). The old password (p1), will be restored to
> -- rolpassword, along with its valid-until value.
> ALTER ROLE u1 PASSWORD NULL;
> -- This may come as a surprise to some users, because currently they expect the
> -- user to completely lose the ability to use passwords for login after this
> -- command. To get the old behavior, the user must now use the ALL PASSWORD
> -- NULL incantation; see below.
> -- Issuing this command one more time will remove even the restored password,
> -- hence leaving the user with no passwords.
Is it possible to remove the oldest password added without removing the
latest password added?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 36+ messages in thread
* Re: [PoC/RFC] Multiple passwords, interval expirations
@ 2023-10-05 20:55 Gurjeet Singh <[email protected]>
parent: Nathan Bossart <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Gurjeet Singh @ 2023-10-05 20:55 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; Stephen Frost <[email protected]>; Brindle, Joshua <[email protected]>; Jeff Davis <[email protected]>
On Thu, Oct 5, 2023 at 12:04 PM Nathan Bossart <[email protected]> wrote:
>
> On Wed, Oct 04, 2023 at 10:41:15PM -0700, Gurjeet Singh wrote:
> > The patches posted in this thread so far attempt to add the ability to
> > allow the user to have an arbitrary number of passwords. I believe
> > that allowing arbitrary number of passwords is not only unnecessary,
> > but the need to name passwords, the need to store them in a shared
> > catalog, etc. may actually create problems in the field. The
> > users/admins will have to choose names for passwords, which they
> > didn't have to previously. The need to name them may also lead to
> > users storing password-hints in the password names (e.g. 'mom''s
> > birthday', 'ex''s phone number', 'third password'), rendering the
> > passwords weak.
> >
> > Moreover, allowing an arbitrarily many number of passwords will
> > require us to provide additional infrastructure to solve problems like
> > observability (which passwords are currently in use, and which ones
> > have been effectively forgotten by applications), or create a nuisance
> > for admins that can create more problems than it solves.
>
> IMHO neither of these problems seems insurmountable.
Agreed.
> Besides advising
> against using hints as names, we could also automatically generate safe
> names, or even disallow user-provided names entirely.
Somehow naming passwords doesn't feel palatable to me.
> And adding
> observability for passwords seems worthwhile anyway.
Agreed.
> > So I propose that the feature should allow no more than 2 passwords
> > for a role, each with their own validity periods. This eliminates the
> > need to name passwords, because at any given time there are no more
> > than 2 passwords; current one, and old one. This also eliminates the
> > need for a shared catalog to hold passwords, because with the limit of
> > 2 imposed, we can store the old password and its validity period in
> > additional columns in the pg_authid table.
>
> Another approach could be to allow an abritrary number of passwords but to
> also allow administrators to limit how many passwords can be associated to
> each role. That way, we needn't restrict this feature to 2 passwords for
> everyone. Perhaps 2 should be the default, but in any case, IMO we
> shouldn't design to only support 2.
I don't see a real use case to support more than 2 passwords. Allowing
an arbitrary number of passwords might look good and clean from
aesthetics and documentation perspective (no artificially enforced
limits, as in the zero-one-infinity rule), but in absence of real use
cases for that many passwords, I'm afraid we might end up with a
feature that creates more and worse problems for the users than it
solves.
> > In essence, we create a stack that can hold 2 passwords. Pushing an
> > element when it's full will make it forget the bottom element. Popping
> > the stack makes it forget the top element, and the only remaining
> > element, if any, becomes the top.
>
> I think this would be mighty confusing to users since it's not clear that
> adding a password will potentially invalidate a current password (which
> might be actively in use), but only if there are already 2 in the stack.
Fair point. We can aid the user by emitting a NOTICE (or a WARNING)
message whenever an old password is removed from the system because of
addition of a new password.
> I
> worry that such a desіgn might be too closely tailored to the
> implementation details. If we proceed with this design, perhaps we should
> consider ERROR-ing if a user tries to add a third password.
I did not have a stack in mind when developing the use case and the
grammar, so implementation details did not drive this design. This new
design was more of a response to the manageability nightmare that I
could see the old approach may lead to. When writing the email I
thought mentioning the stack analogy may make it easier to develop a
mental model. I certainly won't suggest using it in the docs for
explanation :-)
> > -- If, for some reason, the user wants to get rid of the latest password added.
> > -- Remove 'p2' (current password). The old password (p1), will be restored to
> > -- rolpassword, along with its valid-until value.
> > ALTER ROLE u1 PASSWORD NULL;
> > -- This may come as a surprise to some users, because currently they expect the
> > -- user to completely lose the ability to use passwords for login after this
> > -- command. To get the old behavior, the user must now use the ALL PASSWORD
> > -- NULL incantation; see below.
> > -- Issuing this command one more time will remove even the restored password,
> > -- hence leaving the user with no passwords.
>
> Is it possible to remove the oldest password added without removing the
> latest password added?
In the patch I have so far, ALTER ROLE u1 ADD PASSWORD '' (empty
string) will drop the old password (what you asked for), and move the
current password to rololdpassword (which is not exactly what you
asked for :-). Hence the oldest password will be forgotten, and the
current password will continue to work;
Perhaps an explicit syntax like ALTER ROLE u1 DROP OLD PASSWORD can be
used for this.
Best regards,
Gurjeet
http://Gurje.et
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v10 5/7] Row pattern recognition patch (docs).
@ 2023-10-22 02:22 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-10-22 02:22 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 80 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 170 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..cf18dd887e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,86 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. Row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. So in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. In the second or
+ subsequent rows all window functions and aggregates are NULL. For rows
+ that do not match the PATTERN, all window functions and aggregates are
+ shown AS NULL too, except count which shows 0. This is because the
+ unmatched rows are in an empty frame. Example of
+ a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock,
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | |
+ company1 | 2023-07-03 | 150 | | |
+ company1 | 2023-07-04 | 140 | | |
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | |
+ company1 | 2023-07-08 | 130 | | |
+ company1 | 2023-07-09 | 120 | | |
+ company1 | 2023-07-10 | 130 | | | 0
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7c3e940afe..1d835af15a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21878,6 +21878,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21917,6 +21918,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 42d78913cf..522ad9dd70 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Sun_Oct_22_11_39_20_2023_140)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v10-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v11 5/7] Row pattern recognition patch (docs).
@ 2023-11-08 06:57 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-11-08 06:57 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 80 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 170 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..cf18dd887e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,86 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. Row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. So in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. In the second or
+ subsequent rows all window functions and aggregates are NULL. For rows
+ that do not match the PATTERN, all window functions and aggregates are
+ shown AS NULL too, except count which shows 0. This is because the
+ unmatched rows are in an empty frame. Example of
+ a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock,
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | |
+ company1 | 2023-07-03 | 150 | | |
+ company1 | 2023-07-04 | 140 | | |
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | |
+ company1 | 2023-07-08 | 130 | | |
+ company1 | 2023-07-09 | 120 | | |
+ company1 | 2023-07-10 | 130 | | | 0
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d963f0a0a0..c3a8167c8e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21933,6 +21933,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21972,6 +21973,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 42d78913cf..522ad9dd70 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Wed_Nov__8_16_37_05_2023_872)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v11-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v12 5/7] Row pattern recognition patch (docs).
@ 2023-12-04 11:23 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2023-12-04 11:23 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 80 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 170 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..cf18dd887e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,86 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. Row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. So in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. In the second or
+ subsequent rows all window functions and aggregates are NULL. For rows
+ that do not match the PATTERN, all window functions and aggregates are
+ shown AS NULL too, except count which shows 0. This is because the
+ unmatched rows are in an empty frame. Example of
+ a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock,
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | |
+ company1 | 2023-07-03 | 150 | | |
+ company1 | 2023-07-04 | 140 | | |
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | |
+ company1 | 2023-07-08 | 130 | | |
+ company1 | 2023-07-09 | 120 | | |
+ company1 | 2023-07-10 | 130 | | | 0
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 20da3ed033..8a18e0ee23 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21935,6 +21935,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21974,6 +21975,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 227ba1993b..dabaca9127 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Fri_Dec__8_10_16_13_2023_489)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v12-0006-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v13 6/8] Row pattern recognition patch (docs).
@ 2024-01-22 09:45 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2024-01-22 09:45 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 80 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 170 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..cf18dd887e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,86 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. Row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. So in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. In the second or
+ subsequent rows all window functions and aggregates are NULL. For rows
+ that do not match the PATTERN, all window functions and aggregates are
+ shown AS NULL too, except count which shows 0. This is because the
+ unmatched rows are in an empty frame. Example of
+ a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock,
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | |
+ company1 | 2023-07-03 | 150 | | |
+ company1 | 2023-07-04 | 140 | | |
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | |
+ company1 | 2023-07-08 | 130 | | |
+ company1 | 2023-07-09 | 120 | | |
+ company1 | 2023-07-10 | 130 | | | 0
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 210c7c0b02..8422aa6b93 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21941,6 +21941,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -21980,6 +21981,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 9917df7839..1575fc2167 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Mon_Jan_22_19_26_18_2024_011)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v13-0007-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v14 6/8] Row pattern recognition patch (docs).
@ 2024-02-28 13:59 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2024-02-28 13:59 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 80 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 170 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..cf18dd887e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,86 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. Row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. So in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. In the second or
+ subsequent rows all window functions and aggregates are NULL. For rows
+ that do not match the PATTERN, all window functions and aggregates are
+ shown AS NULL too, except count which shows 0. This is because the
+ unmatched rows are in an empty frame. Example of
+ a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock,
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | |
+ company1 | 2023-07-03 | 150 | | |
+ company1 | 2023-07-04 | 140 | | |
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | |
+ company1 | 2023-07-08 | 130 | | |
+ company1 | 2023-07-09 | 120 | | |
+ company1 | 2023-07-10 | 130 | | | 0
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e5fa82c161..8dd97501a7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22364,6 +22364,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -22403,6 +22404,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 9917df7839..1575fc2167 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Thu_Feb_29_09_19_54_2024_640)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v14-0007-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v15 6/8] Row pattern recognition patch (docs).
@ 2024-03-28 10:30 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2024-03-28 10:30 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 80 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 170 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..cf18dd887e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,86 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. Row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. So in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. In the second or
+ subsequent rows all window functions and aggregates are NULL. For rows
+ that do not match the PATTERN, all window functions and aggregates are
+ shown AS NULL too, except count which shows 0. This is because the
+ unmatched rows are in an empty frame. Example of
+ a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock,
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | |
+ company1 | 2023-07-03 | 150 | | |
+ company1 | 2023-07-04 | 140 | | |
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | |
+ company1 | 2023-07-08 | 130 | | |
+ company1 | 2023-07-09 | 120 | | |
+ company1 | 2023-07-10 | 130 | | | 0
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 93b0bc2bc6..d25eeb3327 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22637,6 +22637,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -22676,6 +22677,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 066aed44e6..8f18718d58 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Thu_Mar_28_19_59_25_2024_076)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v15-0007-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v16 6/8] Row pattern recognition patch (docs).
@ 2024-04-12 06:49 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2024-04-12 06:49 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 80 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 170 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..cf18dd887e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,86 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. Row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. So in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. In the second or
+ subsequent rows all window functions and aggregates are NULL. For rows
+ that do not match the PATTERN, all window functions and aggregates are
+ shown AS NULL too, except count which shows 0. This is because the
+ unmatched rows are in an empty frame. Example of
+ a <literal>SELECT</literal> using the <literal>DEFINE</literal>
+ and <literal>PATTERN</literal> clause is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock,
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | |
+ company1 | 2023-07-03 | 150 | | |
+ company1 | 2023-07-04 | 140 | | |
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | |
+ company1 | 2023-07-08 | 130 | | |
+ company1 | 2023-07-09 | 120 | | |
+ company1 | 2023-07-10 | 130 | | | 0
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8dfb42ad4d..f6ee99fe19 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23123,6 +23123,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -23162,6 +23163,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 066aed44e6..8f18718d58 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Fri_Apr_12_16_09_08_2024_262)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v16-0007-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v17 6/8] Row pattern recognition patch (docs).
@ 2024-04-28 11:00 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2024-04-28 11:00 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 82 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..b0b1d1c51e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,88 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. The row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. Thus in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. On the second or
+ subsequent rows all window functions are NULL. Aggregates are NULL or 0
+ (count case) depending on its aggregation definition. For rows that do not
+ match on the PATTERN, all window functions and aggregates are shown AS
+ NULL too, except count showing 0. This is because the rows do not match,
+ thus they are in an empty frame. Example of a <literal>SELECT</literal>
+ using the <literal>DEFINE</literal> and <literal>PATTERN</literal> clause
+ is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | | 0
+ company1 | 2023-07-03 | 150 | | | 0
+ company1 | 2023-07-04 | 140 | | | 0
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | | 0
+ company1 | 2023-07-08 | 130 | | | 0
+ company1 | 2023-07-09 | 120 | | | 0
+ company1 | 2023-07-10 | 130 | | | 0
+(10 rows)
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1928de5762..adbcb1f279 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23124,6 +23124,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -23163,6 +23164,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 066aed44e6..8f18718d58 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Sun_Apr_28_20_28_26_2024_444)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v17-0007-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v18 6/8] Row pattern recognition patch (docs).
@ 2024-05-11 07:11 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2024-05-11 07:11 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 82 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..b0b1d1c51e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,88 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. The row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. Thus in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. On the second or
+ subsequent rows all window functions are NULL. Aggregates are NULL or 0
+ (count case) depending on its aggregation definition. For rows that do not
+ match on the PATTERN, all window functions and aggregates are shown AS
+ NULL too, except count showing 0. This is because the rows do not match,
+ thus they are in an empty frame. Example of a <literal>SELECT</literal>
+ using the <literal>DEFINE</literal> and <literal>PATTERN</literal> clause
+ is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | | 0
+ company1 | 2023-07-03 | 150 | | | 0
+ company1 | 2023-07-04 | 140 | | | 0
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | | 0
+ company1 | 2023-07-08 | 130 | | | 0
+ company1 | 2023-07-09 | 120 | | | 0
+ company1 | 2023-07-10 | 130 | | | 0
+(10 rows)
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 17c44bc338..8dbab31300 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23124,6 +23124,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -23163,6 +23164,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 066aed44e6..8f18718d58 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Sat_May_11_16_23_07_2024_789)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v18-0007-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v19 6/8] Row pattern recognition patch (docs).
@ 2024-05-14 23:26 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2024-05-14 23:26 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 82 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..b0b1d1c51e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,88 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. The row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. Thus in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. On the second or
+ subsequent rows all window functions are NULL. Aggregates are NULL or 0
+ (count case) depending on its aggregation definition. For rows that do not
+ match on the PATTERN, all window functions and aggregates are shown AS
+ NULL too, except count showing 0. This is because the rows do not match,
+ thus they are in an empty frame. Example of a <literal>SELECT</literal>
+ using the <literal>DEFINE</literal> and <literal>PATTERN</literal> clause
+ is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | | 0
+ company1 | 2023-07-03 | 150 | | | 0
+ company1 | 2023-07-04 | 140 | | | 0
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | | 0
+ company1 | 2023-07-08 | 130 | | | 0
+ company1 | 2023-07-09 | 120 | | | 0
+ company1 | 2023-07-10 | 130 | | | 0
+(10 rows)
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 17c44bc338..8dbab31300 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23124,6 +23124,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -23163,6 +23164,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 066aed44e6..8f18718d58 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Wed_May_15_09_02_03_2024_008)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v19-0007-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v20 6/8] Row pattern recognition patch (docs).
@ 2024-05-24 02:26 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2024-05-24 02:26 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 82 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..b0b1d1c51e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,88 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. The row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. Thus in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. On the second or
+ subsequent rows all window functions are NULL. Aggregates are NULL or 0
+ (count case) depending on its aggregation definition. For rows that do not
+ match on the PATTERN, all window functions and aggregates are shown AS
+ NULL too, except count showing 0. This is because the rows do not match,
+ thus they are in an empty frame. Example of a <literal>SELECT</literal>
+ using the <literal>DEFINE</literal> and <literal>PATTERN</literal> clause
+ is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | | 0
+ company1 | 2023-07-03 | 150 | | | 0
+ company1 | 2023-07-04 | 140 | | | 0
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | | 0
+ company1 | 2023-07-08 | 130 | | | 0
+ company1 | 2023-07-09 | 120 | | | 0
+ company1 | 2023-07-10 | 130 | | | 0
+(10 rows)
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 17c44bc338..8dbab31300 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23124,6 +23124,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -23163,6 +23164,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 066aed44e6..8f18718d58 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Fri_May_24_11_39_19_2024_763)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v20-0007-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
* [PATCH v21 6/8] Row pattern recognition patch (docs).
@ 2024-08-26 04:32 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 36+ messages in thread
From: Tatsuo Ishii @ 2024-08-26 04:32 UTC (permalink / raw)
---
doc/src/sgml/advanced.sgml | 82 ++++++++++++++++++++++++++++++++++++
doc/src/sgml/func.sgml | 54 ++++++++++++++++++++++++
doc/src/sgml/ref/select.sgml | 38 ++++++++++++++++-
3 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml
index 755c9f1485..b0b1d1c51e 100644
--- a/doc/src/sgml/advanced.sgml
+++ b/doc/src/sgml/advanced.sgml
@@ -537,6 +537,88 @@ WHERE pos < 3;
<literal>rank</literal> less than 3.
</para>
+ <para>
+ Row pattern common syntax can be used to perform row pattern recognition
+ in a query. The row pattern common syntax includes two sub
+ clauses: <literal>DEFINE</literal>
+ and <literal>PATTERN</literal>. <literal>DEFINE</literal> defines
+ definition variables along with an expression. The expression must be a
+ logical expression, which means it must
+ return <literal>TRUE</literal>, <literal>FALSE</literal>
+ or <literal>NULL</literal>. The expression may comprise column references
+ and functions. Window functions, aggregate functions and subqueries are
+ not allowed. An example of <literal>DEFINE</literal> is as follows.
+
+<programlisting>
+DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+</programlisting>
+
+ Note that <function>PREV</function> returns the price column in the
+ previous row if it's called in a context of row pattern recognition. Thus in
+ the second line the definition variable "UP" is <literal>TRUE</literal>
+ when the price column in the current row is greater than the price column
+ in the previous row. Likewise, "DOWN" is <literal>TRUE</literal> when when
+ the price column in the current row is lower than the price column in the
+ previous row.
+ </para>
+ <para>
+ Once <literal>DEFINE</literal> exists, <literal>PATTERN</literal> can be
+ used. <literal>PATTERN</literal> defines a sequence of rows that satisfies
+ certain conditions. For example following <literal>PATTERN</literal>
+ defines that a row starts with the condition "LOWPRICE", then one or more
+ rows satisfy "UP" and finally one or more rows satisfy "DOWN". Note that
+ "+" means one or more matches. Also you can use "*", which means zero or
+ more matches. If a sequence of rows which satisfies the PATTERN is found,
+ in the starting row of the sequence of rows all window functions and
+ aggregates are shown in the target list. Note that aggregations only look
+ into the matched rows, rather than whole frame. On the second or
+ subsequent rows all window functions are NULL. Aggregates are NULL or 0
+ (count case) depending on its aggregation definition. For rows that do not
+ match on the PATTERN, all window functions and aggregates are shown AS
+ NULL too, except count showing 0. This is because the rows do not match,
+ thus they are in an empty frame. Example of a <literal>SELECT</literal>
+ using the <literal>DEFINE</literal> and <literal>PATTERN</literal> clause
+ is as follows.
+
+<programlisting>
+SELECT company, tdate, price,
+ first_value(price) OVER w,
+ max(price) OVER w,
+ count(price) OVER w
+FROM stock
+ WINDOW w AS (
+ PARTITION BY company
+ ORDER BY tdate
+ ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+ AFTER MATCH SKIP PAST LAST ROW
+ INITIAL
+ PATTERN (LOWPRICE UP+ DOWN+)
+ DEFINE
+ LOWPRICE AS price <= 100,
+ UP AS price > PREV(price),
+ DOWN AS price < PREV(price)
+);
+</programlisting>
+<screen>
+ company | tdate | price | first_value | max | count
+----------+------------+-------+-------------+-----+-------
+ company1 | 2023-07-01 | 100 | 100 | 200 | 4
+ company1 | 2023-07-02 | 200 | | | 0
+ company1 | 2023-07-03 | 150 | | | 0
+ company1 | 2023-07-04 | 140 | | | 0
+ company1 | 2023-07-05 | 150 | | | 0
+ company1 | 2023-07-06 | 90 | 90 | 130 | 4
+ company1 | 2023-07-07 | 110 | | | 0
+ company1 | 2023-07-08 | 130 | | | 0
+ company1 | 2023-07-09 | 120 | | | 0
+ company1 | 2023-07-10 | 130 | | | 0
+(10 rows)
+</screen>
+ </para>
+
<para>
When a query involves multiple window functions, it is possible to write
out each one with a separate <literal>OVER</literal> clause, but this is
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 461fc3f437..02ad2b0195 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23258,6 +23258,7 @@ SELECT count(*) FROM sometable;
returns <literal>NULL</literal> if there is no such row.
</para></entry>
</row>
+
</tbody>
</tgroup>
</table>
@@ -23297,6 +23298,59 @@ SELECT count(*) FROM sometable;
Other frame specifications can be used to obtain other effects.
</para>
+ <para>
+ Row pattern recognition navigation functions are listed in
+ <xref linkend="functions-rpr-navigation-table"/>. These functions
+ can be used to describe DEFINE clause of Row pattern recognition.
+ </para>
+
+ <table id="functions-rpr-navigation-table">
+ <title>Row Pattern Navigation Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>prev</primary>
+ </indexterm>
+ <function>prev</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the previous row;
+ returns NULL if there is no previous row in the window frame.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>next</primary>
+ </indexterm>
+ <function>next</function> ( <parameter>value</parameter> <type>anyelement</type> )
+ <returnvalue>anyelement</returnvalue>
+ </para>
+ <para>
+ Returns the column value at the next row;
+ returns NULL if there is no next row in the window frame.
+ </para></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<note>
<para>
The SQL standard defines a <literal>RESPECT NULLS</literal> or
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index d7089eac0b..7e1c9989ba 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -969,8 +969,8 @@ WINDOW <replaceable class="parameter">window_name</replaceable> AS ( <replaceabl
The <replaceable class="parameter">frame_clause</replaceable> can be one of
<synopsis>
-{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
-{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ]
+{ RANGE | ROWS | GROUPS } <replaceable>frame_start</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
+{ RANGE | ROWS | GROUPS } BETWEEN <replaceable>frame_start</replaceable> AND <replaceable>frame_end</replaceable> [ <replaceable>frame_exclusion</replaceable> ] [row_pattern_common_syntax]
</synopsis>
where <replaceable>frame_start</replaceable>
@@ -1077,6 +1077,40 @@ EXCLUDE NO OTHERS
a given peer group will be in the frame or excluded from it.
</para>
+ <para>
+ The
+ optional <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ defines the <firstterm>row pattern recognition condition</firstterm> for
+ this
+ window. <replaceable class="parameter">row_pattern_common_syntax</replaceable>
+ includes following subclauses. <literal>AFTER MATCH SKIP PAST LAST
+ ROW</literal> or <literal>AFTER MATCH SKIP TO NEXT ROW</literal> controls
+ how to proceed to next row position after a match
+ found. With <literal>AFTER MATCH SKIP PAST LAST ROW</literal> (the
+ default) next row position is next to the last row of previous match. On
+ the other hand, with <literal>AFTER MATCH SKIP TO NEXT ROW</literal> next
+ row position is always next to the last row of previous
+ match. <literal>DEFINE</literal> defines definition variables along with a
+ boolean expression. <literal>PATTERN</literal> defines a sequence of rows
+ that satisfies certain conditions using variables defined
+ in <literal>DEFINE</literal> clause. If the variable is not defined in
+ the <literal>DEFINE</literal> clause, it is implicitly assumed
+ following is defined in the <literal>DEFINE</literal> clause.
+
+<synopsis>
+<literal>variable_name</literal> AS TRUE
+</synopsis>
+
+ Note that the maximu number of variables defined
+ in <literal>DEFINE</literal> clause is 26.
+
+<synopsis>
+[ AFTER MATCH SKIP PAST LAST ROW | AFTER MATCH SKIP TO NEXT ROW ]
+PATTERN <replaceable class="parameter">pattern_variable_name</replaceable>[+] [, ...]
+DEFINE <replaceable class="parameter">definition_varible_name</replaceable> AS <replaceable class="parameter">expression</replaceable> [, ...]
+</synopsis>
+ </para>
+
<para>
The purpose of a <literal>WINDOW</literal> clause is to specify the
behavior of <firstterm>window functions</firstterm> appearing in the query's
--
2.25.1
----Next_Part(Mon_Aug_26_13_39_47_2024_878)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v21-0007-Row-pattern-recognition-patch-tests.patch"
^ permalink raw reply [nested|flat] 36+ messages in thread
end of thread, other threads:[~2024-08-26 04:32 UTC | newest]
Thread overview: 36+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2022-03-02 18:02 Re: [PoC/RFC] Multiple passwords, interval expirations Joshua Brindle <[email protected]>
2022-04-08 17:04 ` Joshua Brindle <[email protected]>
2022-06-29 21:21 ` Jacob Champion <[email protected]>
2022-06-29 21:27 ` Stephen Frost <[email protected]>
2022-06-30 23:53 ` Gurjeet Singh <[email protected]>
2022-07-01 00:20 ` Stephen Frost <[email protected]>
2022-07-01 14:50 ` Brindle, Joshua <[email protected]>
2022-07-01 15:13 ` Stephen Frost <[email protected]>
2023-09-25 07:31 ` Gurjeet Singh <[email protected]>
2023-09-26 23:36 ` Jeff Davis <[email protected]>
2023-10-05 05:41 ` Gurjeet Singh <[email protected]>
2023-10-05 19:04 ` Nathan Bossart <[email protected]>
2023-10-05 20:55 ` Gurjeet Singh <[email protected]>
2023-06-25 11:48 [PATCH v1 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-06-26 08:05 [PATCH v2 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-07-19 15:54 [PATCH v2] Compare only major versions in AdjustUpgrade.pm Alvaro Herrera <[email protected]>
2023-07-19 15:54 [PATCH v2] Compare only major versions in AdjustUpgrade.pm Alvaro Herrera <[email protected]>
2023-07-26 10:49 [PATCH v3 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-08-09 07:56 [PATCH v4 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-09-02 06:32 [PATCH v5 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-09-12 05:22 [PATCH v6 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-09-22 04:53 [PATCH v7 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-09-25 05:01 [PATCH v8 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-10-04 05:51 [PATCH v9 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-10-22 02:22 [PATCH v10 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-11-08 06:57 [PATCH v11 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2023-12-04 11:23 [PATCH v12 5/7] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2024-01-22 09:45 [PATCH v13 6/8] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2024-02-28 13:59 [PATCH v14 6/8] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2024-03-28 10:30 [PATCH v15 6/8] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2024-04-12 06:49 [PATCH v16 6/8] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2024-04-28 11:00 [PATCH v17 6/8] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2024-05-11 07:11 [PATCH v18 6/8] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2024-05-14 23:26 [PATCH v19 6/8] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2024-05-24 02:26 [PATCH v20 6/8] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
2024-08-26 04:32 [PATCH v21 6/8] Row pattern recognition patch (docs). Tatsuo Ishii <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox