mirror of https://github.com/citusdata/citus.git
Adds Grant Role support from non-main db (#7404)
DESCRIPTION: Adds support for distributed role-membership management commands from the databases where Citus is not installed (`GRANT <role> TO <role>`) This PR also refactors the code-path that allows executing some of the node-wide commands so that we use send deparsed query string to other nodes instead of the `queryString` passed into utility hook. --------- Co-authored-by: Onur Tirtir <onurcantirtir@gmail.com>pull/7513/head^2
parent
9a0cdbf5af
commit
2cbfdbfa46
|
@ -94,6 +94,37 @@
|
||||||
#define MARK_OBJECT_DISTRIBUTED \
|
#define MARK_OBJECT_DISTRIBUTED \
|
||||||
"SELECT citus_internal.mark_object_distributed(%d, %s, %d, %s)"
|
"SELECT citus_internal.mark_object_distributed(%d, %s, %d, %s)"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NonMainDbDistributedStatementInfo is used to determine whether a statement is
|
||||||
|
* supported from non-main databases and whether it should be marked as
|
||||||
|
* distributed explicitly (*).
|
||||||
|
*
|
||||||
|
* (*) 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.
|
||||||
|
*/
|
||||||
|
typedef struct NonMainDbDistributedStatementInfo
|
||||||
|
{
|
||||||
|
int statementType;
|
||||||
|
bool explicitlyMarkAsDistributed;
|
||||||
|
} NonMainDbDistributedStatementInfo;
|
||||||
|
|
||||||
|
typedef struct MarkObjectDistributedParams
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
Oid id;
|
||||||
|
uint16 catalogRelId;
|
||||||
|
} MarkObjectDistributedParams;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NonMainDbSupportedStatements is an array of statements that are supported
|
||||||
|
* from non-main databases.
|
||||||
|
*/
|
||||||
|
static const NonMainDbDistributedStatementInfo NonMainDbSupportedStatements[] = {
|
||||||
|
{ T_GrantRoleStmt, false },
|
||||||
|
{ T_CreateRoleStmt, true }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
bool EnableDDLPropagation = true; /* ddl propagation is enabled */
|
bool EnableDDLPropagation = true; /* ddl propagation is enabled */
|
||||||
int CreateObjectPropagationMode = CREATE_OBJECT_PROPAGATION_IMMEDIATE;
|
int CreateObjectPropagationMode = CREATE_OBJECT_PROPAGATION_IMMEDIATE;
|
||||||
|
@ -122,8 +153,12 @@ static void PostStandardProcessUtility(Node *parsetree);
|
||||||
static void DecrementUtilityHookCountersIfNecessary(Node *parsetree);
|
static void DecrementUtilityHookCountersIfNecessary(Node *parsetree);
|
||||||
static bool IsDropSchemaOrDB(Node *parsetree);
|
static bool IsDropSchemaOrDB(Node *parsetree);
|
||||||
static bool ShouldCheckUndistributeCitusLocalTables(void);
|
static bool ShouldCheckUndistributeCitusLocalTables(void);
|
||||||
static void RunPreprocessMainDBCommand(Node *parsetree, const char *queryString);
|
static void RunPreprocessMainDBCommand(Node *parsetree);
|
||||||
static void RunPostprocessMainDBCommand(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);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ProcessUtilityParseTree is a convenience method to create a PlannedStmt out of
|
* ProcessUtilityParseTree is a convenience method to create a PlannedStmt out of
|
||||||
|
@ -257,7 +292,7 @@ citus_ProcessUtility(PlannedStmt *pstmt,
|
||||||
{
|
{
|
||||||
if (!IsMainDB)
|
if (!IsMainDB)
|
||||||
{
|
{
|
||||||
RunPreprocessMainDBCommand(parsetree, queryString);
|
RunPreprocessMainDBCommand(parsetree);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1608,22 +1643,25 @@ DropSchemaOrDBInProgress(void)
|
||||||
* database before query is run on the local node with PrevProcessUtility
|
* database before query is run on the local node with PrevProcessUtility
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
RunPreprocessMainDBCommand(Node *parsetree, const char *queryString)
|
RunPreprocessMainDBCommand(Node *parsetree)
|
||||||
{
|
{
|
||||||
if (IsA(parsetree, CreateRoleStmt))
|
if (!IsStatementSupportedFromNonMainDb(parsetree))
|
||||||
{
|
{
|
||||||
StringInfo mainDBQuery = makeStringInfo();
|
return;
|
||||||
appendStringInfo(mainDBQuery,
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *queryString = DeparseTreeNode(parsetree);
|
||||||
|
StringInfo mainDBQuery = makeStringInfo();
|
||||||
|
appendStringInfo(mainDBQuery,
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1634,17 +1672,98 @@ RunPreprocessMainDBCommand(Node *parsetree, const char *queryString)
|
||||||
static void
|
static void
|
||||||
RunPostprocessMainDBCommand(Node *parsetree)
|
RunPostprocessMainDBCommand(Node *parsetree)
|
||||||
{
|
{
|
||||||
if (IsA(parsetree, CreateRoleStmt))
|
if (IsStatementSupportedFromNonMainDb(parsetree) &&
|
||||||
|
StatementRequiresMarkDistributedFromNonMainDb(parsetree))
|
||||||
{
|
{
|
||||||
StringInfo mainDBQuery = makeStringInfo();
|
MarkObjectDistributedFromNonMainDb(parsetree);
|
||||||
CreateRoleStmt *createRoleStmt = castNode(CreateRoleStmt, parsetree);
|
|
||||||
Oid roleOid = get_role_oid(createRoleStmt->role, false);
|
|
||||||
appendStringInfo(mainDBQuery,
|
|
||||||
MARK_OBJECT_DISTRIBUTED,
|
|
||||||
AuthIdRelationId,
|
|
||||||
quote_literal_cstr(createRoleStmt->role),
|
|
||||||
roleOid,
|
|
||||||
quote_literal_cstr(CurrentUserName()));
|
|
||||||
RunCitusMainDBQuery(mainDBQuery->data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IsStatementSupportedFromNonMainDb returns true if the statement is supported from a
|
||||||
|
* non-main database.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
IsStatementSupportedFromNonMainDb(Node *parsetree)
|
||||||
|
{
|
||||||
|
NodeTag type = nodeTag(parsetree);
|
||||||
|
|
||||||
|
for (int i = 0; i < sizeof(NonMainDbSupportedStatements) /
|
||||||
|
sizeof(NonMainDbSupportedStatements[0]); i++)
|
||||||
|
{
|
||||||
|
if (type == NonMainDbSupportedStatements[i].statementType)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* StatementRequiresMarkDistributedFromNonMainDb returns true if the statement should be marked
|
||||||
|
* as distributed when executed from a non-main database.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
StatementRequiresMarkDistributedFromNonMainDb(Node *parsetree)
|
||||||
|
{
|
||||||
|
NodeTag type = nodeTag(parsetree);
|
||||||
|
|
||||||
|
for (int i = 0; i < sizeof(NonMainDbSupportedStatements) /
|
||||||
|
sizeof(NonMainDbSupportedStatements[0]); i++)
|
||||||
|
{
|
||||||
|
if (type == NonMainDbSupportedStatements[i].statementType)
|
||||||
|
{
|
||||||
|
return NonMainDbSupportedStatements[i].explicitlyMarkAsDistributed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MarkObjectDistributedFromNonMainDb marks the given object as distributed on the
|
||||||
|
* non-main database.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
MarkObjectDistributedFromNonMainDb(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetMarkObjectDistributedParams returns MarkObjectDistributedParams for the target
|
||||||
|
* object of given parsetree.
|
||||||
|
*/
|
||||||
|
static MarkObjectDistributedParams
|
||||||
|
GetMarkObjectDistributedParams(Node *parsetree)
|
||||||
|
{
|
||||||
|
if (IsA(parsetree, CreateRoleStmt))
|
||||||
|
{
|
||||||
|
CreateRoleStmt *stmt = castNode(CreateRoleStmt, parsetree);
|
||||||
|
MarkObjectDistributedParams info = {
|
||||||
|
.name = stmt->role,
|
||||||
|
.catalogRelId = AuthIdRelationId,
|
||||||
|
.id = get_role_oid(stmt->role, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add else if branches for other statement types */
|
||||||
|
|
||||||
|
elog(ERROR, "unsupported statement type");
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
CREATE SCHEMA grant_role2pc;
|
||||||
|
SET search_path TO grant_role2pc;
|
||||||
|
set citus.enable_create_database_propagation to on;
|
||||||
|
CREATE DATABASE grant_role2pc_db;
|
||||||
|
\c grant_role2pc_db
|
||||||
|
SHOW citus.main_db;
|
||||||
|
citus.main_db
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
regression
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SET citus.superuser TO 'postgres';
|
||||||
|
CREATE USER grant_role2pc_user1;
|
||||||
|
CREATE USER grant_role2pc_user2;
|
||||||
|
CREATE USER grant_role2pc_user3;
|
||||||
|
CREATE USER grant_role2pc_user4;
|
||||||
|
CREATE USER grant_role2pc_user5;
|
||||||
|
CREATE USER grant_role2pc_user6;
|
||||||
|
CREATE USER grant_role2pc_user7;
|
||||||
|
\c grant_role2pc_db
|
||||||
|
--test with empty superuser
|
||||||
|
SET citus.superuser TO '';
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user2;
|
||||||
|
ERROR: No superuser role is given for Citus main database connection
|
||||||
|
HINT: Set citus.superuser to a superuser role name
|
||||||
|
SET citus.superuser TO 'postgres';
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user2 with admin option granted by CURRENT_USER;
|
||||||
|
\c regression
|
||||||
|
select result FROM run_command_on_all_nodes(
|
||||||
|
$$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text = 'grant_role2pc_user2'
|
||||||
|
order by member::regrole::text, roleid::regrole::text
|
||||||
|
) t
|
||||||
|
$$
|
||||||
|
);
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
[{"member":"grant_role2pc_user2","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true}]
|
||||||
|
[{"member":"grant_role2pc_user2","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true}]
|
||||||
|
[{"member":"grant_role2pc_user2","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true}]
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
\c grant_role2pc_db
|
||||||
|
--test grant under transactional context with multiple operations
|
||||||
|
BEGIN;
|
||||||
|
grant grant_role2pc_user1,grant_role2pc_user2 to grant_role2pc_user3 WITH ADMIN OPTION;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user4 granted by grant_role2pc_user3 ;
|
||||||
|
COMMIT;
|
||||||
|
BEGIN;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user5 WITH ADMIN OPTION granted by grant_role2pc_user3;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user6;
|
||||||
|
ROLLBACK;
|
||||||
|
BEGIN;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user7;
|
||||||
|
SELECT 1/0;
|
||||||
|
ERROR: division by zero
|
||||||
|
commit;
|
||||||
|
\c regression
|
||||||
|
select result FROM run_command_on_all_nodes($$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text in
|
||||||
|
('grant_role2pc_user3','grant_role2pc_user4','grant_role2pc_user5','grant_role2pc_user6','grant_role2pc_user7')
|
||||||
|
order by member::regrole::text, roleid::regrole::text
|
||||||
|
) t
|
||||||
|
$$);
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
[{"member":"grant_role2pc_user3","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user3","role":"grant_role2pc_user2","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user4","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
[{"member":"grant_role2pc_user3","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user3","role":"grant_role2pc_user2","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user4","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
[{"member":"grant_role2pc_user3","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user3","role":"grant_role2pc_user2","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user4","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
\c grant_role2pc_db
|
||||||
|
grant grant_role2pc_user1,grant_role2pc_user2 to grant_role2pc_user5,grant_role2pc_user6,grant_role2pc_user7 granted by grant_role2pc_user3;
|
||||||
|
\c regression
|
||||||
|
select result FROM run_command_on_all_nodes($$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text in
|
||||||
|
('grant_role2pc_user5','grant_role2pc_user6','grant_role2pc_user7')
|
||||||
|
order by member::regrole::text, roleid::regrole::text
|
||||||
|
) t
|
||||||
|
$$);
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
[{"member":"grant_role2pc_user5","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user5","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user7","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user7","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
[{"member":"grant_role2pc_user5","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user5","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user7","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user7","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
[{"member":"grant_role2pc_user5","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user5","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user7","role":"grant_role2pc_user1","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user7","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
\c grant_role2pc_db
|
||||||
|
revoke admin option for grant_role2pc_user1 from grant_role2pc_user5 granted by grant_role2pc_user3;
|
||||||
|
--test revoke under transactional context with multiple operations
|
||||||
|
BEGIN;
|
||||||
|
revoke grant_role2pc_user1 from grant_role2pc_user5 granted by grant_role2pc_user3 ;
|
||||||
|
revoke grant_role2pc_user1 from grant_role2pc_user4 granted by grant_role2pc_user3;
|
||||||
|
COMMIT;
|
||||||
|
\c grant_role2pc_db - - :worker_1_port
|
||||||
|
BEGIN;
|
||||||
|
revoke grant_role2pc_user1 from grant_role2pc_user6,grant_role2pc_user7 granted by grant_role2pc_user3;
|
||||||
|
revoke grant_role2pc_user1 from grant_role2pc_user3 cascade;
|
||||||
|
COMMIT;
|
||||||
|
\c regression
|
||||||
|
select result FROM run_command_on_all_nodes($$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text in
|
||||||
|
('grant_role2pc_user2','grant_role2pc_user3','grant_role2pc_user4','grant_role2pc_user5','grant_role2pc_user6','grant_role2pc_user7')
|
||||||
|
order by member::regrole::text, roleid::regrole::text
|
||||||
|
) t
|
||||||
|
$$);
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
[{"member":"grant_role2pc_user2","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user3","role":"grant_role2pc_user2","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user5","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user7","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
[{"member":"grant_role2pc_user2","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user3","role":"grant_role2pc_user2","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user5","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user7","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
[{"member":"grant_role2pc_user2","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user3","role":"grant_role2pc_user2","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user5","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user7","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
\c grant_role2pc_db - - :worker_1_port
|
||||||
|
BEGIN;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user5 WITH ADMIN OPTION;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user6;
|
||||||
|
COMMIT;
|
||||||
|
\c regression - - :master_port
|
||||||
|
select result FROM run_command_on_all_nodes($$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text in
|
||||||
|
('grant_role2pc_user5','grant_role2pc_user6')
|
||||||
|
order by member::regrole::text, roleid::regrole::text
|
||||||
|
) t
|
||||||
|
$$);
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
[{"member":"grant_role2pc_user5","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user5","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user1","grantor":"postgres","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
[{"member":"grant_role2pc_user5","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user5","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user1","grantor":"postgres","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
[{"member":"grant_role2pc_user5","role":"grant_role2pc_user1","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user5","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user1","grantor":"postgres","admin_option":false},{"member":"grant_role2pc_user6","role":"grant_role2pc_user2","grantor":"grant_role2pc_user3","admin_option":false}]
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
revoke grant_role2pc_user1 from grant_role2pc_user5,grant_role2pc_user6;
|
||||||
|
--clean resources
|
||||||
|
DROP SCHEMA grant_role2pc;
|
||||||
|
set citus.enable_create_database_propagation to on;
|
||||||
|
DROP DATABASE grant_role2pc_db;
|
||||||
|
drop user grant_role2pc_user2,grant_role2pc_user3,grant_role2pc_user4,grant_role2pc_user5,grant_role2pc_user6,grant_role2pc_user7;
|
||||||
|
drop user grant_role2pc_user1;
|
||||||
|
reset citus.enable_create_database_propagation;
|
|
@ -0,0 +1,61 @@
|
||||||
|
CREATE SCHEMA metadata_sync_2pc_schema;
|
||||||
|
SET search_path TO metadata_sync_2pc_schema;
|
||||||
|
set citus.enable_create_database_propagation to on;
|
||||||
|
CREATE DATABASE metadata_sync_2pc_db;
|
||||||
|
\c metadata_sync_2pc_db
|
||||||
|
SHOW citus.main_db;
|
||||||
|
citus.main_db
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
regression
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
CREATE USER "grant_role2pc'_user1";
|
||||||
|
CREATE USER "grant_role2pc'_user2";
|
||||||
|
CREATE USER "grant_role2pc'_user3";
|
||||||
|
CREATE USER grant_role2pc_user4;
|
||||||
|
CREATE USER grant_role2pc_user5;
|
||||||
|
\c regression
|
||||||
|
select 1 from citus_remove_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
\c metadata_sync_2pc_db
|
||||||
|
grant "grant_role2pc'_user1","grant_role2pc'_user2" to "grant_role2pc'_user3" WITH ADMIN OPTION;
|
||||||
|
grant "grant_role2pc'_user1","grant_role2pc'_user2" to grant_role2pc_user4,grant_role2pc_user5 granted by "grant_role2pc'_user3";
|
||||||
|
\c regression
|
||||||
|
select 1 from citus_add_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
select result FROM run_command_on_all_nodes($$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text in
|
||||||
|
('"grant_role2pc''_user2"','"grant_role2pc''_user3"','grant_role2pc_user4','grant_role2pc_user5')
|
||||||
|
order by member::regrole::text
|
||||||
|
) t
|
||||||
|
$$);
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
[{"member":"\"grant_role2pc'_user3\"","role":"\"grant_role2pc'_user1\"","grantor":"postgres","admin_option":true},{"member":"\"grant_role2pc'_user3\"","role":"\"grant_role2pc'_user2\"","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user4","role":"\"grant_role2pc'_user1\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false},{"member":"grant_role2pc_user4","role":"\"grant_role2pc'_user2\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false},{"member":"grant_role2pc_user5","role":"\"grant_role2pc'_user1\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false},{"member":"grant_role2pc_user5","role":"\"grant_role2pc'_user2\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false}]
|
||||||
|
[{"member":"\"grant_role2pc'_user3\"","role":"\"grant_role2pc'_user1\"","grantor":"postgres","admin_option":true},{"member":"\"grant_role2pc'_user3\"","role":"\"grant_role2pc'_user2\"","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user4","role":"\"grant_role2pc'_user1\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false},{"member":"grant_role2pc_user4","role":"\"grant_role2pc'_user2\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false},{"member":"grant_role2pc_user5","role":"\"grant_role2pc'_user1\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false},{"member":"grant_role2pc_user5","role":"\"grant_role2pc'_user2\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false}]
|
||||||
|
[{"member":"\"grant_role2pc'_user3\"","role":"\"grant_role2pc'_user1\"","grantor":"postgres","admin_option":true},{"member":"\"grant_role2pc'_user3\"","role":"\"grant_role2pc'_user2\"","grantor":"postgres","admin_option":true},{"member":"grant_role2pc_user4","role":"\"grant_role2pc'_user1\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false},{"member":"grant_role2pc_user4","role":"\"grant_role2pc'_user2\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false},{"member":"grant_role2pc_user5","role":"\"grant_role2pc'_user1\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false},{"member":"grant_role2pc_user5","role":"\"grant_role2pc'_user2\"","grantor":"\"grant_role2pc'_user3\"","admin_option":false}]
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
\c metadata_sync_2pc_db
|
||||||
|
revoke "grant_role2pc'_user1","grant_role2pc'_user2" from grant_role2pc_user4,grant_role2pc_user5 granted by "grant_role2pc'_user3";
|
||||||
|
revoke admin option for "grant_role2pc'_user1","grant_role2pc'_user2" from "grant_role2pc'_user3";
|
||||||
|
revoke "grant_role2pc'_user1","grant_role2pc'_user2" from "grant_role2pc'_user3";
|
||||||
|
\c regression
|
||||||
|
drop user "grant_role2pc'_user1","grant_role2pc'_user2","grant_role2pc'_user3",grant_role2pc_user4,grant_role2pc_user5;
|
||||||
|
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;
|
|
@ -625,4 +625,16 @@ BEGIN
|
||||||
) q2
|
) q2
|
||||||
JOIN pg_dist_node USING (nodeid);
|
JOIN pg_dist_node USING (nodeid);
|
||||||
END;
|
END;
|
||||||
$func$ LANGUAGE plpgsql;
|
$func$ LANGUAGE plpgsql;
|
||||||
|
CREATE OR REPLACE FUNCTION check_database_privileges(role_name text, db_name text, permissions text[])
|
||||||
|
RETURNS TABLE(permission text, result text)
|
||||||
|
AS $func$
|
||||||
|
DECLARE
|
||||||
|
permission text;
|
||||||
|
BEGIN
|
||||||
|
FOREACH permission IN ARRAY permissions
|
||||||
|
LOOP
|
||||||
|
RETURN QUERY EXECUTE format($inner$SELECT '%s', result FROM run_command_on_all_nodes($$select has_database_privilege('%s','%s', '%s'); $$)$inner$, permission, role_name, db_name, permission);
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$func$ LANGUAGE plpgsql;
|
||||||
|
|
|
@ -40,6 +40,7 @@ test: create_drop_database_propagation_pg15
|
||||||
test: create_drop_database_propagation_pg16
|
test: create_drop_database_propagation_pg16
|
||||||
test: comment_on_database
|
test: comment_on_database
|
||||||
test: comment_on_role
|
test: comment_on_role
|
||||||
|
test: metadata_sync_from_non_maindb
|
||||||
# don't parallelize single_shard_table_udfs to make sure colocation ids are sequential
|
# don't parallelize single_shard_table_udfs to make sure colocation ids are sequential
|
||||||
test: single_shard_table_udfs
|
test: single_shard_table_udfs
|
||||||
test: schema_based_sharding
|
test: schema_based_sharding
|
||||||
|
|
|
@ -108,7 +108,7 @@ test: object_propagation_debug
|
||||||
test: undistribute_table
|
test: undistribute_table
|
||||||
test: run_command_on_all_nodes
|
test: run_command_on_all_nodes
|
||||||
test: background_task_queue_monitor
|
test: background_task_queue_monitor
|
||||||
test: other_databases
|
test: other_databases grant_role_from_non_maindb
|
||||||
test: citus_internal_access
|
test: citus_internal_access
|
||||||
|
|
||||||
# Causal clock test
|
# Causal clock test
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
CREATE SCHEMA grant_role2pc;
|
||||||
|
SET search_path TO grant_role2pc;
|
||||||
|
set citus.enable_create_database_propagation to on;
|
||||||
|
|
||||||
|
CREATE DATABASE grant_role2pc_db;
|
||||||
|
|
||||||
|
\c grant_role2pc_db
|
||||||
|
SHOW citus.main_db;
|
||||||
|
|
||||||
|
SET citus.superuser TO 'postgres';
|
||||||
|
CREATE USER grant_role2pc_user1;
|
||||||
|
CREATE USER grant_role2pc_user2;
|
||||||
|
CREATE USER grant_role2pc_user3;
|
||||||
|
CREATE USER grant_role2pc_user4;
|
||||||
|
CREATE USER grant_role2pc_user5;
|
||||||
|
CREATE USER grant_role2pc_user6;
|
||||||
|
CREATE USER grant_role2pc_user7;
|
||||||
|
|
||||||
|
\c grant_role2pc_db
|
||||||
|
|
||||||
|
--test with empty superuser
|
||||||
|
SET citus.superuser TO '';
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user2;
|
||||||
|
|
||||||
|
SET citus.superuser TO 'postgres';
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user2 with admin option granted by CURRENT_USER;
|
||||||
|
|
||||||
|
\c regression
|
||||||
|
|
||||||
|
select result FROM run_command_on_all_nodes(
|
||||||
|
$$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text = 'grant_role2pc_user2'
|
||||||
|
order by member::regrole::text, roleid::regrole::text
|
||||||
|
) t
|
||||||
|
$$
|
||||||
|
);
|
||||||
|
|
||||||
|
\c grant_role2pc_db
|
||||||
|
--test grant under transactional context with multiple operations
|
||||||
|
BEGIN;
|
||||||
|
grant grant_role2pc_user1,grant_role2pc_user2 to grant_role2pc_user3 WITH ADMIN OPTION;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user4 granted by grant_role2pc_user3 ;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user5 WITH ADMIN OPTION granted by grant_role2pc_user3;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user6;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user7;
|
||||||
|
SELECT 1/0;
|
||||||
|
commit;
|
||||||
|
|
||||||
|
|
||||||
|
\c regression
|
||||||
|
|
||||||
|
select result FROM run_command_on_all_nodes($$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text in
|
||||||
|
('grant_role2pc_user3','grant_role2pc_user4','grant_role2pc_user5','grant_role2pc_user6','grant_role2pc_user7')
|
||||||
|
order by member::regrole::text, roleid::regrole::text
|
||||||
|
) t
|
||||||
|
$$);
|
||||||
|
|
||||||
|
|
||||||
|
\c grant_role2pc_db
|
||||||
|
|
||||||
|
grant grant_role2pc_user1,grant_role2pc_user2 to grant_role2pc_user5,grant_role2pc_user6,grant_role2pc_user7 granted by grant_role2pc_user3;
|
||||||
|
|
||||||
|
\c regression
|
||||||
|
|
||||||
|
select result FROM run_command_on_all_nodes($$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text in
|
||||||
|
('grant_role2pc_user5','grant_role2pc_user6','grant_role2pc_user7')
|
||||||
|
order by member::regrole::text, roleid::regrole::text
|
||||||
|
) t
|
||||||
|
$$);
|
||||||
|
|
||||||
|
\c grant_role2pc_db
|
||||||
|
revoke admin option for grant_role2pc_user1 from grant_role2pc_user5 granted by grant_role2pc_user3;
|
||||||
|
|
||||||
|
--test revoke under transactional context with multiple operations
|
||||||
|
BEGIN;
|
||||||
|
revoke grant_role2pc_user1 from grant_role2pc_user5 granted by grant_role2pc_user3 ;
|
||||||
|
revoke grant_role2pc_user1 from grant_role2pc_user4 granted by grant_role2pc_user3;
|
||||||
|
COMMIT;
|
||||||
|
\c grant_role2pc_db - - :worker_1_port
|
||||||
|
BEGIN;
|
||||||
|
revoke grant_role2pc_user1 from grant_role2pc_user6,grant_role2pc_user7 granted by grant_role2pc_user3;
|
||||||
|
revoke grant_role2pc_user1 from grant_role2pc_user3 cascade;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
\c regression
|
||||||
|
|
||||||
|
select result FROM run_command_on_all_nodes($$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text in
|
||||||
|
('grant_role2pc_user2','grant_role2pc_user3','grant_role2pc_user4','grant_role2pc_user5','grant_role2pc_user6','grant_role2pc_user7')
|
||||||
|
order by member::regrole::text, roleid::regrole::text
|
||||||
|
) t
|
||||||
|
$$);
|
||||||
|
|
||||||
|
\c grant_role2pc_db - - :worker_1_port
|
||||||
|
BEGIN;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user5 WITH ADMIN OPTION;
|
||||||
|
grant grant_role2pc_user1 to grant_role2pc_user6;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
\c regression - - :master_port
|
||||||
|
|
||||||
|
select result FROM run_command_on_all_nodes($$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text in
|
||||||
|
('grant_role2pc_user5','grant_role2pc_user6')
|
||||||
|
order by member::regrole::text, roleid::regrole::text
|
||||||
|
) t
|
||||||
|
$$);
|
||||||
|
|
||||||
|
revoke grant_role2pc_user1 from grant_role2pc_user5,grant_role2pc_user6;
|
||||||
|
|
||||||
|
--clean resources
|
||||||
|
DROP SCHEMA grant_role2pc;
|
||||||
|
set citus.enable_create_database_propagation to on;
|
||||||
|
DROP DATABASE grant_role2pc_db;
|
||||||
|
drop user grant_role2pc_user2,grant_role2pc_user3,grant_role2pc_user4,grant_role2pc_user5,grant_role2pc_user6,grant_role2pc_user7;
|
||||||
|
drop user grant_role2pc_user1;
|
||||||
|
reset citus.enable_create_database_propagation;
|
|
@ -0,0 +1,51 @@
|
||||||
|
CREATE SCHEMA metadata_sync_2pc_schema;
|
||||||
|
SET search_path TO metadata_sync_2pc_schema;
|
||||||
|
set citus.enable_create_database_propagation to on;
|
||||||
|
CREATE DATABASE metadata_sync_2pc_db;
|
||||||
|
|
||||||
|
\c metadata_sync_2pc_db
|
||||||
|
SHOW citus.main_db;
|
||||||
|
|
||||||
|
CREATE USER "grant_role2pc'_user1";
|
||||||
|
CREATE USER "grant_role2pc'_user2";
|
||||||
|
CREATE USER "grant_role2pc'_user3";
|
||||||
|
CREATE USER grant_role2pc_user4;
|
||||||
|
CREATE USER grant_role2pc_user5;
|
||||||
|
|
||||||
|
\c regression
|
||||||
|
select 1 from citus_remove_node('localhost', :worker_2_port);
|
||||||
|
|
||||||
|
\c metadata_sync_2pc_db
|
||||||
|
grant "grant_role2pc'_user1","grant_role2pc'_user2" to "grant_role2pc'_user3" WITH ADMIN OPTION;
|
||||||
|
grant "grant_role2pc'_user1","grant_role2pc'_user2" to grant_role2pc_user4,grant_role2pc_user5 granted by "grant_role2pc'_user3";
|
||||||
|
|
||||||
|
\c regression
|
||||||
|
select 1 from citus_add_node('localhost', :worker_2_port);
|
||||||
|
|
||||||
|
select result FROM run_command_on_all_nodes($$
|
||||||
|
SELECT array_to_json(array_agg(row_to_json(t)))
|
||||||
|
FROM (
|
||||||
|
SELECT member::regrole, roleid::regrole as role, grantor::regrole, admin_option
|
||||||
|
FROM pg_auth_members
|
||||||
|
WHERE member::regrole::text in
|
||||||
|
('"grant_role2pc''_user2"','"grant_role2pc''_user3"','grant_role2pc_user4','grant_role2pc_user5')
|
||||||
|
order by member::regrole::text
|
||||||
|
) t
|
||||||
|
$$);
|
||||||
|
|
||||||
|
\c metadata_sync_2pc_db
|
||||||
|
revoke "grant_role2pc'_user1","grant_role2pc'_user2" from grant_role2pc_user4,grant_role2pc_user5 granted by "grant_role2pc'_user3";
|
||||||
|
|
||||||
|
revoke admin option for "grant_role2pc'_user1","grant_role2pc'_user2" from "grant_role2pc'_user3";
|
||||||
|
|
||||||
|
revoke "grant_role2pc'_user1","grant_role2pc'_user2" from "grant_role2pc'_user3";
|
||||||
|
|
||||||
|
\c regression
|
||||||
|
|
||||||
|
drop user "grant_role2pc'_user1","grant_role2pc'_user2","grant_role2pc'_user3",grant_role2pc_user4,grant_role2pc_user5;
|
||||||
|
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;
|
|
@ -652,3 +652,16 @@ BEGIN
|
||||||
JOIN pg_dist_node USING (nodeid);
|
JOIN pg_dist_node USING (nodeid);
|
||||||
END;
|
END;
|
||||||
$func$ LANGUAGE plpgsql;
|
$func$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION check_database_privileges(role_name text, db_name text, permissions text[])
|
||||||
|
RETURNS TABLE(permission text, result text)
|
||||||
|
AS $func$
|
||||||
|
DECLARE
|
||||||
|
permission text;
|
||||||
|
BEGIN
|
||||||
|
FOREACH permission IN ARRAY permissions
|
||||||
|
LOOP
|
||||||
|
RETURN QUERY EXECUTE format($inner$SELECT '%s', result FROM run_command_on_all_nodes($$select has_database_privilege('%s','%s', '%s'); $$)$inner$, permission, role_name, db_name, permission);
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$func$ LANGUAGE plpgsql;
|
||||||
|
|
Loading…
Reference in New Issue