Add support for alter/drop role propagation from non-main databases (#7461)

DESCRIPTION: Adds support for distributed `ALTER/DROP ROLE` commands
from the databases where Citus is not installed

---------

Co-authored-by: Onur Tirtir <onurcantirtir@gmail.com>
pull/7547/head
Gürkan İndibay 2024-02-28 11:58:28 +03:00 committed by GitHub
parent f4242685e3
commit 51009d0191
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 713 additions and 90 deletions

View File

@ -93,20 +93,40 @@
"SELECT citus_internal.start_management_transaction('%lu')"
#define MARK_OBJECT_DISTRIBUTED \
"SELECT citus_internal.mark_object_distributed(%d, %s, %d, %s)"
#define UNMARK_OBJECT_DISTRIBUTED \
"SELECT pg_catalog.citus_unmark_object_distributed(%d, %d, %d,%s)"
/* see NonMainDbDistributedStatementInfo for the explanation of these flags */
typedef enum DistObjectOperation
{
NO_DIST_OBJECT_OPERATION,
MARK_DISTRIBUTED_GLOBALLY,
UNMARK_DISTRIBUTED_LOCALLY
} DistObjectOperation;
/*
* NonMainDbDistributedStatementInfo is used to determine whether a statement is
* supported from non-main databases and whether it should be marked as
* distributed explicitly (*).
* supported from non-main databases and whether it should be marked or unmarked
* as distributed.
*
* (*) We always have to mark such objects as "distributed" but while for some
* object types we can delegate this to main database, for some others we have
* to explicitly send a command to all nodes in this code-path to achieve this.
* When creating a distributed object, we always have to mark such objects as
* "distributed" but while for some object types we can delegate this to main
* database, for some others we have to explicitly send a command to all nodes
* in this code-path to achieve this. Callers need to provide
* MARK_DISTRIBUTED_GLOBALLY when that is the case.
*
* Similarly when dropping a distributed object, we always have to unmark such
* objects as "distributed" and our utility hook on remote nodes achieve this
* via UnmarkNodeWideObjectsDistributed() because the commands that we send to
* workers are executed via main db. However for the local node, this is not the
* case as we're not in the main db. For this reason, callers need to provide
* UNMARK_DISTRIBUTED_LOCALLY to unmark an object for local node as well.
*/
typedef struct NonMainDbDistributedStatementInfo
{
int statementType;
bool explicitlyMarkAsDistributed;
DistObjectOperation DistObjectOperation;
/*
* checkSupportedObjectTypes is a callback function that checks whether
@ -118,15 +138,16 @@ typedef struct NonMainDbDistributedStatementInfo
} NonMainDbDistributedStatementInfo;
/*
* MarkObjectDistributedParams is used to pass parameters to the
* MarkObjectDistributedFromNonMainDb function.
* DistObjectOperationParams is used to pass parameters to the
* MarkObjectDistributedGloballyFromNonMainDb function and
* UnMarkObjectDistributedLocallyFromNonMainDb functions.
*/
typedef struct MarkObjectDistributedParams
typedef struct DistObjectOperationParams
{
char *name;
Oid id;
uint16 catalogRelId;
} MarkObjectDistributedParams;
} DistObjectOperationParams;
bool EnableDDLPropagation = true; /* ddl propagation is enabled */
@ -166,9 +187,11 @@ static bool IsCommandToCreateOrDropMainDB(Node *parsetree);
static void RunPreprocessMainDBCommand(Node *parsetree);
static void RunPostprocessMainDBCommand(Node *parsetree);
static bool IsStatementSupportedFromNonMainDb(Node *parsetree);
static bool StatementRequiresMarkDistributedFromNonMainDb(Node *parsetree);
static void MarkObjectDistributedFromNonMainDb(Node *parsetree);
static MarkObjectDistributedParams GetMarkObjectDistributedParams(Node *parsetree);
static bool StatementRequiresMarkDistributedGloballyFromNonMainDb(Node *parsetree);
static bool StatementRequiresUnmarkDistributedLocallyFromNonMainDb(Node *parsetree);
static void MarkObjectDistributedGloballyFromNonMainDb(Node *parsetree);
static void UnMarkObjectDistributedLocallyFromNonMainDb(List *unmarkDistributedList);
static List * GetDistObjectOperationParams(Node *parsetree);
/*
* checkSupportedObjectTypes callbacks for
@ -184,12 +207,15 @@ static bool NonMainDbCheckSupportedObjectTypeForSecLabel(Node *node);
*/
ObjectType supportedObjectTypesForGrantStmt[] = { OBJECT_DATABASE };
static const NonMainDbDistributedStatementInfo NonMainDbSupportedStatements[] = {
{ T_GrantRoleStmt, false, NULL },
{ T_CreateRoleStmt, true, NULL },
{ T_GrantStmt, false, NonMainDbCheckSupportedObjectTypeForGrant },
{ T_CreatedbStmt, false, NULL },
{ T_DropdbStmt, false, NULL },
{ T_SecLabelStmt, false, NonMainDbCheckSupportedObjectTypeForSecLabel },
{ T_GrantRoleStmt, NO_DIST_OBJECT_OPERATION, NULL },
{ T_CreateRoleStmt, MARK_DISTRIBUTED_GLOBALLY, NULL },
{ T_DropRoleStmt, UNMARK_DISTRIBUTED_LOCALLY, NULL },
{ T_AlterRoleStmt, NO_DIST_OBJECT_OPERATION, NULL },
{ T_GrantStmt, NO_DIST_OBJECT_OPERATION, NonMainDbCheckSupportedObjectTypeForGrant },
{ T_CreatedbStmt, NO_DIST_OBJECT_OPERATION, NULL },
{ T_DropdbStmt, NO_DIST_OBJECT_OPERATION, NULL },
{ T_SecLabelStmt, NO_DIST_OBJECT_OPERATION,
NonMainDbCheckSupportedObjectTypeForSecLabel },
};
@ -1743,12 +1769,19 @@ RunPreprocessMainDBCommand(Node *parsetree)
START_MANAGEMENT_TRANSACTION,
GetCurrentFullTransactionId().value);
RunCitusMainDBQuery(mainDBQuery->data);
mainDBQuery = makeStringInfo();
appendStringInfo(mainDBQuery,
EXECUTE_COMMAND_ON_REMOTE_NODES_AS_USER,
quote_literal_cstr(queryString),
quote_literal_cstr(CurrentUserName()));
RunCitusMainDBQuery(mainDBQuery->data);
if (StatementRequiresUnmarkDistributedLocallyFromNonMainDb(parsetree))
{
List *unmarkParams = GetDistObjectOperationParams(parsetree);
UnMarkObjectDistributedLocallyFromNonMainDb(unmarkParams);
}
}
@ -1760,9 +1793,9 @@ static void
RunPostprocessMainDBCommand(Node *parsetree)
{
if (IsStatementSupportedFromNonMainDb(parsetree) &&
StatementRequiresMarkDistributedFromNonMainDb(parsetree))
StatementRequiresMarkDistributedGloballyFromNonMainDb(parsetree))
{
MarkObjectDistributedFromNonMainDb(parsetree);
MarkObjectDistributedGloballyFromNonMainDb(parsetree);
}
}
@ -1793,11 +1826,11 @@ IsStatementSupportedFromNonMainDb(Node *parsetree)
/*
* StatementRequiresMarkDistributedFromNonMainDb returns true if the statement should be marked
* StatementRequiresMarkDistributedGloballyFromNonMainDb returns true if the statement should be marked
* as distributed when executed from a non-main database.
*/
static bool
StatementRequiresMarkDistributedFromNonMainDb(Node *parsetree)
StatementRequiresMarkDistributedGloballyFromNonMainDb(Node *parsetree)
{
NodeTag type = nodeTag(parsetree);
@ -1806,7 +1839,8 @@ StatementRequiresMarkDistributedFromNonMainDb(Node *parsetree)
{
if (type == NonMainDbSupportedStatements[i].statementType)
{
return NonMainDbSupportedStatements[i].explicitlyMarkAsDistributed;
return NonMainDbSupportedStatements[i].DistObjectOperation ==
MARK_DISTRIBUTED_GLOBALLY;
}
}
@ -1815,47 +1849,125 @@ StatementRequiresMarkDistributedFromNonMainDb(Node *parsetree)
/*
* MarkObjectDistributedFromNonMainDb marks the given object as distributed on the
* non-main database.
* StatementRequiresUnmarkDistributedLocallyFromNonMainDb returns true if the statement should be unmarked
* as distributed when executed from a non-main database.
*/
static void
MarkObjectDistributedFromNonMainDb(Node *parsetree)
static bool
StatementRequiresUnmarkDistributedLocallyFromNonMainDb(Node *parsetree)
{
MarkObjectDistributedParams markObjectDistributedParams =
GetMarkObjectDistributedParams(parsetree);
StringInfo mainDBQuery = makeStringInfo();
appendStringInfo(mainDBQuery,
MARK_OBJECT_DISTRIBUTED,
markObjectDistributedParams.catalogRelId,
quote_literal_cstr(markObjectDistributedParams.name),
markObjectDistributedParams.id,
quote_literal_cstr(CurrentUserName()));
RunCitusMainDBQuery(mainDBQuery->data);
NodeTag type = nodeTag(parsetree);
for (int i = 0; i < sizeof(NonMainDbSupportedStatements) /
sizeof(NonMainDbSupportedStatements[0]); i++)
{
if (type == NonMainDbSupportedStatements[i].statementType)
{
return NonMainDbSupportedStatements[i].DistObjectOperation ==
UNMARK_DISTRIBUTED_LOCALLY;
}
}
return false;
}
/*
* GetMarkObjectDistributedParams returns MarkObjectDistributedParams for the target
* MarkObjectDistributedGloballyFromNonMainDb marks the given object as distributed on the
* non-main database.
*/
static void
MarkObjectDistributedGloballyFromNonMainDb(Node *parsetree)
{
List *distObjectOperationParams =
GetDistObjectOperationParams(parsetree);
DistObjectOperationParams *distObjectOperationParam = NULL;
foreach_ptr(distObjectOperationParam, distObjectOperationParams)
{
StringInfo mainDBQuery = makeStringInfo();
appendStringInfo(mainDBQuery,
MARK_OBJECT_DISTRIBUTED,
distObjectOperationParam->catalogRelId,
quote_literal_cstr(distObjectOperationParam->name),
distObjectOperationParam->id,
quote_literal_cstr(CurrentUserName()));
RunCitusMainDBQuery(mainDBQuery->data);
}
}
/*
* UnMarkObjectDistributedLocallyFromNonMainDb unmarks the given object as distributed on the
* non-main database.
*/
static void
UnMarkObjectDistributedLocallyFromNonMainDb(List *markObjectDistributedParamList)
{
DistObjectOperationParams *markObjectDistributedParam = NULL;
int subObjectId = 0;
char *checkObjectExistence = "false";
foreach_ptr(markObjectDistributedParam, markObjectDistributedParamList)
{
StringInfo query = makeStringInfo();
appendStringInfo(query,
UNMARK_OBJECT_DISTRIBUTED,
AuthIdRelationId,
markObjectDistributedParam->id,
subObjectId, checkObjectExistence);
RunCitusMainDBQuery(query->data);
}
}
/*
* GetDistObjectOperationParams returns DistObjectOperationParams for the target
* object of given parsetree.
*/
static MarkObjectDistributedParams
GetMarkObjectDistributedParams(Node *parsetree)
List *
GetDistObjectOperationParams(Node *parsetree)
{
List *paramsList = NIL;
if (IsA(parsetree, CreateRoleStmt))
{
CreateRoleStmt *stmt = castNode(CreateRoleStmt, parsetree);
MarkObjectDistributedParams info = {
.name = stmt->role,
.catalogRelId = AuthIdRelationId,
.id = get_role_oid(stmt->role, false)
};
DistObjectOperationParams *params =
(DistObjectOperationParams *) palloc(sizeof(DistObjectOperationParams));
params->name = stmt->role;
params->catalogRelId = AuthIdRelationId;
params->id = get_role_oid(stmt->role, false);
return info;
paramsList = lappend(paramsList, params);
}
else if (IsA(parsetree, DropRoleStmt))
{
DropRoleStmt *stmt = castNode(DropRoleStmt, parsetree);
RoleSpec *roleSpec;
foreach_ptr(roleSpec, stmt->roles)
{
DistObjectOperationParams *params = (DistObjectOperationParams *) palloc(
sizeof(DistObjectOperationParams));
Oid roleOid = get_role_oid(roleSpec->rolename, true);
if (roleOid == InvalidOid)
{
continue;
}
/* Add else if branches for other statement types */
params->id = roleOid;
params->name = roleSpec->rolename;
params->catalogRelId = AuthIdRelationId;
paramsList = lappend(paramsList, params);
}
}
else
{
elog(ERROR, "unsupported statement type");
}
return paramsList;
}

View File

@ -98,10 +98,10 @@ mark_object_distributed(PG_FUNCTION_ARGS)
/*
* citus_unmark_object_distributed(classid oid, objid oid, objsubid int)
* citus_unmark_object_distributed(classid oid, objid oid, objsubid int,checkobjectexistence bool)
*
* removes the entry for an object address from pg_dist_object. Only removes the entry if
* the object does not exist anymore.
* Removes the entry for an object address from pg_dist_object. If checkobjectexistence is true,
* throws an error if the object still exists.
*/
Datum
citus_unmark_object_distributed(PG_FUNCTION_ARGS)
@ -109,6 +109,12 @@ citus_unmark_object_distributed(PG_FUNCTION_ARGS)
Oid classid = PG_GETARG_OID(0);
Oid objid = PG_GETARG_OID(1);
int32 objsubid = PG_GETARG_INT32(2);
bool checkObjectExistence = true;
if (!PG_ARGISNULL(3))
{
checkObjectExistence = PG_GETARG_BOOL(3);
}
ObjectAddress address = { 0 };
ObjectAddressSubSet(address, classid, objid, objsubid);
@ -119,7 +125,7 @@ citus_unmark_object_distributed(PG_FUNCTION_ARGS)
PG_RETURN_VOID();
}
if (ObjectExists(&address))
if (checkObjectExistence && ObjectExists(&address))
{
ereport(ERROR, (errmsg("object still exists"),
errdetail("the %s \"%s\" still exists",

View File

@ -7,6 +7,8 @@
#include "udfs/start_management_transaction/12.2-1.sql"
#include "udfs/execute_command_on_remote_nodes_as_user/12.2-1.sql"
#include "udfs/mark_object_distributed/12.2-1.sql"
DROP FUNCTION pg_catalog.citus_unmark_object_distributed(oid, oid, int);
#include "udfs/citus_unmark_object_distributed/12.2-1.sql"
#include "udfs/commit_management_command_2pc/12.2-1.sql"
ALTER TABLE pg_catalog.pg_dist_transaction ADD COLUMN outer_xid xid8;

View File

@ -18,6 +18,9 @@ DROP FUNCTION citus_internal.mark_object_distributed(
classId Oid, objectName text, objectId Oid, connectionUser text
);
DROP FUNCTION pg_catalog.citus_unmark_object_distributed(oid,oid,int,boolean);
#include "../udfs/citus_unmark_object_distributed/10.0-1.sql"
DROP FUNCTION citus_internal.commit_management_command_2pc();
ALTER TABLE pg_catalog.pg_dist_transaction DROP COLUMN outer_xid;

View File

@ -0,0 +1,7 @@
CREATE FUNCTION pg_catalog.citus_unmark_object_distributed(classid oid, objid oid, objsubid int, checkobjectexistence boolean DEFAULT true)
RETURNS void
LANGUAGE C STRICT
AS 'MODULE_PATHNAME', $$citus_unmark_object_distributed$$;
COMMENT ON FUNCTION pg_catalog.citus_unmark_object_distributed(classid oid, objid oid, objsubid int, checkobjectexistence boolean)
IS 'Removes an object from citus.pg_dist_object after deletion. If checkobjectexistence is true, object existence check performed.'
'Otherwise, object existence check is skipped.';

View File

@ -1,6 +1,7 @@
CREATE FUNCTION pg_catalog.citus_unmark_object_distributed(classid oid, objid oid, objsubid int)
CREATE FUNCTION pg_catalog.citus_unmark_object_distributed(classid oid, objid oid, objsubid int, checkobjectexistence boolean DEFAULT true)
RETURNS void
LANGUAGE C STRICT
AS 'MODULE_PATHNAME', $$citus_unmark_object_distributed$$;
COMMENT ON FUNCTION pg_catalog.citus_unmark_object_distributed(classid oid, objid oid, objsubid int)
IS 'remove an object address from citus.pg_dist_object once the object has been deleted';
COMMENT ON FUNCTION pg_catalog.citus_unmark_object_distributed(classid oid, objid oid, objsubid int, checkobjectexistence boolean)
IS 'Removes an object from citus.pg_dist_object after deletion. If checkobjectexistence is true, object existence check performed.'
'Otherwise, object existence check is skipped.';

View File

@ -164,6 +164,149 @@ revoke CONNECT on database metadata_sync_2pc_db from "grant_role2pc'_user2";
revoke CREATE on database metadata_sync_2pc_db from "grant_role2pc'_user1";
\c regression
drop user "grant_role2pc'_user1","grant_role2pc'_user2","grant_role2pc'_user3",grant_role2pc_user4,grant_role2pc_user5;
--test for user operations
--test for create user
\c regression - - :master_port
select 1 from citus_remove_node('localhost', :worker_2_port);
?column?
---------------------------------------------------------------------
1
(1 row)
\c metadata_sync_2pc_db - - :master_port
CREATE ROLE test_role1 WITH LOGIN PASSWORD 'password1';
\c metadata_sync_2pc_db - - :worker_1_port
CREATE USER "test_role2-needs\!escape"
WITH
SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION
LIMIT 10 VALID UNTIL '2023-01-01' IN ROLE test_role1;
create role test_role3;
\c regression - - :master_port
select 1 from citus_add_node('localhost', :worker_2_port);
?column?
---------------------------------------------------------------------
1
(1 row)
-- XXX: date is not correct on one of the workers due to https://github.com/citusdata/citus/issues/7533
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape','test_role3')
ORDER BY rolname
) t
$$);
result
---------------------------------------------------------------------
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":null},{"rolname":"test_role2-needs\\!escape","rolsuper":true,"rolinherit":true,"rolcreaterole":true,"rolcreatedb":true,"rolcanlogin":true,"rolreplication":true,"rolbypassrls":true,"rolconnlimit":10,"pass_not_empty":null,"date":"2023-01-01"},{"rolname":"test_role3","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":null,"date":null}]
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":null},{"rolname":"test_role2-needs\\!escape","rolsuper":true,"rolinherit":true,"rolcreaterole":true,"rolcreatedb":true,"rolcanlogin":true,"rolreplication":true,"rolbypassrls":true,"rolconnlimit":10,"pass_not_empty":null,"date":"2023-01-01"},{"rolname":"test_role3","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":null,"date":null}]
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":"infinity"},{"rolname":"test_role2-needs\\!escape","rolsuper":true,"rolinherit":true,"rolcreaterole":true,"rolcreatedb":true,"rolcanlogin":true,"rolreplication":true,"rolbypassrls":true,"rolconnlimit":10,"pass_not_empty":null,"date":"2023-01-01"},{"rolname":"test_role3","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":null,"date":"infinity"}]
(3 rows)
--test for alter user
select 1 from citus_remove_node('localhost', :worker_2_port);
?column?
---------------------------------------------------------------------
1
(1 row)
\c metadata_sync_2pc_db - - :master_port
-- Test ALTER ROLE with various options
ALTER ROLE test_role1 WITH PASSWORD 'new_password1';
\c metadata_sync_2pc_db - - :worker_1_port
ALTER USER "test_role2-needs\!escape"
WITH
NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION
LIMIT 5 VALID UNTIL '2024-01-01';
\c regression - - :master_port
select 1 from citus_add_node('localhost', :worker_2_port);
?column?
---------------------------------------------------------------------
1
(1 row)
-- XXX: date is not correct on one of the workers due to https://github.com/citusdata/citus/issues/7533
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape','test_role3')
ORDER BY rolname
) t
$$);
result
---------------------------------------------------------------------
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":null},{"rolname":"test_role2-needs\\!escape","rolsuper":false,"rolinherit":false,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":5,"pass_not_empty":null,"date":"2024-01-01"},{"rolname":"test_role3","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":null,"date":null}]
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":null},{"rolname":"test_role2-needs\\!escape","rolsuper":false,"rolinherit":false,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":5,"pass_not_empty":null,"date":"2024-01-01"},{"rolname":"test_role3","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":null,"date":null}]
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":"infinity"},{"rolname":"test_role2-needs\\!escape","rolsuper":false,"rolinherit":false,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":5,"pass_not_empty":null,"date":"2024-01-01"},{"rolname":"test_role3","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":null,"date":"infinity"}]
(3 rows)
--test for drop user
select 1 from citus_remove_node('localhost', :worker_2_port);
?column?
---------------------------------------------------------------------
1
(1 row)
\c metadata_sync_2pc_db - - :worker_1_port
DROP ROLE test_role1, "test_role2-needs\!escape";
\c metadata_sync_2pc_db - - :master_port
DROP ROLE test_role3;
\c regression - - :master_port
select 1 from citus_add_node('localhost', :worker_2_port);
?column?
---------------------------------------------------------------------
1
(1 row)
-- XXX: date is not correct on one of the workers due to https://github.com/citusdata/citus/issues/7533
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape','test_role3')
ORDER BY rolname
) t
$$);
result
---------------------------------------------------------------------
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":"infinity"},{"rolname":"test_role2-needs\\!escape","rolsuper":false,"rolinherit":false,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":5,"pass_not_empty":null,"date":"2024-01-01"},{"rolname":"test_role3","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":null,"date":"infinity"}]
(3 rows)
-- Clean up: drop the database on worker node 2
\c regression - - :worker_2_port
DROP ROLE if exists test_role1, "test_role2-needs\!escape", test_role3;
\c regression - - :master_port
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape','test_role3')
ORDER BY rolname
) t
$$);
result
---------------------------------------------------------------------
(3 rows)
set citus.enable_create_database_propagation to on;
drop database metadata_sync_2pc_db;
drop schema metadata_sync_2pc_schema;

View File

@ -1422,6 +1422,7 @@ ALTER EXTENSION citus UPDATE TO '12.2-1';
SELECT * FROM multi_extension.print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
function citus_unmark_object_distributed(oid,oid,integer) void |
| function citus_internal.acquire_citus_advisory_object_class_lock(integer,cstring) void
| function citus_internal.add_colocation_metadata(integer,integer,integer,regtype,oid) void
| function citus_internal.add_object_metadata(text,text[],text[],integer,integer,boolean) void
@ -1450,7 +1451,8 @@ SELECT * FROM multi_extension.print_extension_changes();
| function citus_internal.update_none_dist_table_metadata(oid,"char",bigint,boolean) void
| function citus_internal.update_placement_metadata(bigint,integer,integer) void
| function citus_internal.update_relation_colocation(oid,integer) void
(28 rows)
| function citus_unmark_object_distributed(oid,oid,integer,boolean) void
(30 rows)
DROP TABLE multi_extension.prev_objects, multi_extension.extension_diff;
-- show running version

View File

@ -0,0 +1,138 @@
-- Create a new database
set citus.enable_create_database_propagation to on;
CREATE DATABASE role_operations_test_db;
SET citus.superuser TO 'postgres';
-- Connect to the new database
\c role_operations_test_db
-- Test CREATE ROLE with various options
CREATE ROLE test_role1 WITH LOGIN PASSWORD 'password1';
\c role_operations_test_db - - :worker_1_port
CREATE USER "test_role2-needs\!escape"
WITH
SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION
LIMIT 10 VALID UNTIL '2023-01-01' IN ROLE test_role1;
\c regression - - :master_port
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape')
ORDER BY rolname
) t
$$);
result
---------------------------------------------------------------------
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":null},{"rolname":"test_role2-needs\\!escape","rolsuper":true,"rolinherit":true,"rolcreaterole":true,"rolcreatedb":true,"rolcanlogin":true,"rolreplication":true,"rolbypassrls":true,"rolconnlimit":10,"pass_not_empty":null,"date":"2023-01-01"}]
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":null},{"rolname":"test_role2-needs\\!escape","rolsuper":true,"rolinherit":true,"rolcreaterole":true,"rolcreatedb":true,"rolcanlogin":true,"rolreplication":true,"rolbypassrls":true,"rolconnlimit":10,"pass_not_empty":null,"date":"2023-01-01"}]
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":null},{"rolname":"test_role2-needs\\!escape","rolsuper":true,"rolinherit":true,"rolcreaterole":true,"rolcreatedb":true,"rolcanlogin":true,"rolreplication":true,"rolbypassrls":true,"rolconnlimit":10,"pass_not_empty":null,"date":"2023-01-01"}]
(3 rows)
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT r.rolname
FROM pg_dist_object d
JOIN pg_roles r ON d.objid = r.oid
WHERE r.rolname IN ('test_role1', 'test_role2-needs\!escape')
order by r.rolname
) t
$$);
result
---------------------------------------------------------------------
[{"rolname":"test_role1"},{"rolname":"test_role2-needs\\!escape"}]
[{"rolname":"test_role1"},{"rolname":"test_role2-needs\\!escape"}]
[{"rolname":"test_role1"},{"rolname":"test_role2-needs\\!escape"}]
(3 rows)
\c role_operations_test_db - - :master_port
-- Test ALTER ROLE with various options
ALTER ROLE test_role1 WITH PASSWORD 'new_password1';
\c role_operations_test_db - - :worker_1_port
ALTER USER "test_role2-needs\!escape"
WITH
NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION
LIMIT 5 VALID UNTIL '2024-01-01';
\c regression - - :master_port
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape')
ORDER BY rolname
) t
$$);
result
---------------------------------------------------------------------
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":null},{"rolname":"test_role2-needs\\!escape","rolsuper":false,"rolinherit":false,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":5,"pass_not_empty":null,"date":"2024-01-01"}]
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":null},{"rolname":"test_role2-needs\\!escape","rolsuper":false,"rolinherit":false,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":5,"pass_not_empty":null,"date":"2024-01-01"}]
[{"rolname":"test_role1","rolsuper":false,"rolinherit":true,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":true,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":-1,"pass_not_empty":true,"date":null},{"rolname":"test_role2-needs\\!escape","rolsuper":false,"rolinherit":false,"rolcreaterole":false,"rolcreatedb":false,"rolcanlogin":false,"rolreplication":false,"rolbypassrls":false,"rolconnlimit":5,"pass_not_empty":null,"date":"2024-01-01"}]
(3 rows)
\c role_operations_test_db - - :master_port
-- Test DROP ROLE
DROP ROLE no_such_role; -- fails nicely
ERROR: role "no_such_role" does not exist
DROP ROLE IF EXISTS no_such_role; -- doesn't fail
NOTICE: role "no_such_role" does not exist, skipping
CREATE ROLE new_role;
DROP ROLE IF EXISTS no_such_role, new_role; -- doesn't fail
NOTICE: role "no_such_role" does not exist, skipping
DROP ROLE IF EXISTS test_role1, "test_role2-needs\!escape";
\c regression - - :master_port
--verify that roles and dist_object are dropped
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape','new_role','no_such_role')
ORDER BY rolname
) t
$$);
result
---------------------------------------------------------------------
(3 rows)
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT r.rolname
FROM pg_roles r
WHERE r.rolname IN ('test_role1', 'test_role2-needs\!escape','new_role','no_such_role')
order by r.rolname
) t
$$);
result
---------------------------------------------------------------------
(3 rows)
SELECT result FROM run_command_on_all_nodes($$
SELECT count(*) leaked_pg_dist_object_records_for_roles
FROM pg_dist_object LEFT JOIN pg_authid ON (objid = oid)
WHERE classid = 1260 AND oid IS NULL
$$);
result
---------------------------------------------------------------------
0
0
0
(3 rows)
-- Clean up: drop the database
set citus.enable_create_database_propagation to on;
DROP DATABASE role_operations_test_db;
reset citus.enable_create_database_propagation;

View File

@ -174,7 +174,7 @@ ORDER BY 1;
function citus_text_send_as_jsonb(text)
function citus_total_relation_size(regclass,boolean)
function citus_truncate_trigger()
function citus_unmark_object_distributed(oid,oid,integer)
function citus_unmark_object_distributed(oid,oid,integer,boolean)
function citus_update_node(integer,text,integer,boolean,integer)
function citus_update_shard_statistics(bigint)
function citus_update_table_statistics(regclass)

View File

@ -108,7 +108,7 @@ test: object_propagation_debug
test: undistribute_table
test: run_command_on_all_nodes
test: background_task_queue_monitor
test: other_databases grant_role_from_non_maindb seclabel_non_maindb
test: other_databases grant_role_from_non_maindb role_operations_from_non_maindb seclabel_non_maindb
test: citus_internal_access
# Causal clock test

View File

@ -80,9 +80,112 @@ revoke CREATE on database metadata_sync_2pc_db from "grant_role2pc'_user1";
\c regression
drop user "grant_role2pc'_user1","grant_role2pc'_user2","grant_role2pc'_user3",grant_role2pc_user4,grant_role2pc_user5;
--test for user operations
--test for create user
\c regression - - :master_port
select 1 from citus_remove_node('localhost', :worker_2_port);
\c metadata_sync_2pc_db - - :master_port
CREATE ROLE test_role1 WITH LOGIN PASSWORD 'password1';
\c metadata_sync_2pc_db - - :worker_1_port
CREATE USER "test_role2-needs\!escape"
WITH
SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION
LIMIT 10 VALID UNTIL '2023-01-01' IN ROLE test_role1;
create role test_role3;
\c regression - - :master_port
select 1 from citus_add_node('localhost', :worker_2_port);
-- XXX: date is not correct on one of the workers due to https://github.com/citusdata/citus/issues/7533
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape','test_role3')
ORDER BY rolname
) t
$$);
--test for alter user
select 1 from citus_remove_node('localhost', :worker_2_port);
\c metadata_sync_2pc_db - - :master_port
-- Test ALTER ROLE with various options
ALTER ROLE test_role1 WITH PASSWORD 'new_password1';
\c metadata_sync_2pc_db - - :worker_1_port
ALTER USER "test_role2-needs\!escape"
WITH
NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION
LIMIT 5 VALID UNTIL '2024-01-01';
\c regression - - :master_port
select 1 from citus_add_node('localhost', :worker_2_port);
-- XXX: date is not correct on one of the workers due to https://github.com/citusdata/citus/issues/7533
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape','test_role3')
ORDER BY rolname
) t
$$);
--test for drop user
select 1 from citus_remove_node('localhost', :worker_2_port);
\c metadata_sync_2pc_db - - :worker_1_port
DROP ROLE test_role1, "test_role2-needs\!escape";
\c metadata_sync_2pc_db - - :master_port
DROP ROLE test_role3;
\c regression - - :master_port
select 1 from citus_add_node('localhost', :worker_2_port);
-- XXX: date is not correct on one of the workers due to https://github.com/citusdata/citus/issues/7533
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape','test_role3')
ORDER BY rolname
) t
$$);
-- Clean up: drop the database on worker node 2
\c regression - - :worker_2_port
DROP ROLE if exists test_role1, "test_role2-needs\!escape", test_role3;
\c regression - - :master_port
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape','test_role3')
ORDER BY rolname
) t
$$);
set citus.enable_create_database_propagation to on;
drop database metadata_sync_2pc_db;
drop schema metadata_sync_2pc_schema;
reset citus.enable_create_database_propagation;
reset search_path;

View File

@ -0,0 +1,106 @@
-- Create a new database
set citus.enable_create_database_propagation to on;
CREATE DATABASE role_operations_test_db;
SET citus.superuser TO 'postgres';
-- Connect to the new database
\c role_operations_test_db
-- Test CREATE ROLE with various options
CREATE ROLE test_role1 WITH LOGIN PASSWORD 'password1';
\c role_operations_test_db - - :worker_1_port
CREATE USER "test_role2-needs\!escape"
WITH
SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION
LIMIT 10 VALID UNTIL '2023-01-01' IN ROLE test_role1;
\c regression - - :master_port
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape')
ORDER BY rolname
) t
$$);
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT r.rolname
FROM pg_dist_object d
JOIN pg_roles r ON d.objid = r.oid
WHERE r.rolname IN ('test_role1', 'test_role2-needs\!escape')
order by r.rolname
) t
$$);
\c role_operations_test_db - - :master_port
-- Test ALTER ROLE with various options
ALTER ROLE test_role1 WITH PASSWORD 'new_password1';
\c role_operations_test_db - - :worker_1_port
ALTER USER "test_role2-needs\!escape"
WITH
NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION
LIMIT 5 VALID UNTIL '2024-01-01';
\c regression - - :master_port
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape')
ORDER BY rolname
) t
$$);
\c role_operations_test_db - - :master_port
-- Test DROP ROLE
DROP ROLE no_such_role; -- fails nicely
DROP ROLE IF EXISTS no_such_role; -- doesn't fail
CREATE ROLE new_role;
DROP ROLE IF EXISTS no_such_role, new_role; -- doesn't fail
DROP ROLE IF EXISTS test_role1, "test_role2-needs\!escape";
\c regression - - :master_port
--verify that roles and dist_object are dropped
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb,
rolcanlogin, rolreplication, rolbypassrls, rolconnlimit,
(rolpassword != '') as pass_not_empty, DATE(rolvaliduntil)
FROM pg_authid
WHERE rolname in ('test_role1', 'test_role2-needs\!escape','new_role','no_such_role')
ORDER BY rolname
) t
$$);
select result FROM run_command_on_all_nodes($$
SELECT array_to_json(array_agg(row_to_json(t)))
FROM (
SELECT r.rolname
FROM pg_roles r
WHERE r.rolname IN ('test_role1', 'test_role2-needs\!escape','new_role','no_such_role')
order by r.rolname
) t
$$);
SELECT result FROM run_command_on_all_nodes($$
SELECT count(*) leaked_pg_dist_object_records_for_roles
FROM pg_dist_object LEFT JOIN pg_authid ON (objid = oid)
WHERE classid = 1260 AND oid IS NULL
$$);
-- Clean up: drop the database
set citus.enable_create_database_propagation to on;
DROP DATABASE role_operations_test_db;
reset citus.enable_create_database_propagation;