Skip to content

Commit c3579ee

Browse files
Adds REASSIGN OWNED BY propagation (#7319)
DESCRIPTION: Adds REASSIGN OWNED BY propagation This pull request introduces the propagation of the "Reassign owned by" statement. It accommodates both local and distributed roles for both the old and new assignments. However, when the old role is a local role, it undergoes filtering and is not propagated. On the other hand, if the new role is a local role, the process involves first creating the role on worker nodes before propagating the "Reassign owned" statement.
1 parent 181b8ab commit c3579ee

16 files changed

+605
-61
lines changed

src/backend/distributed/commands/dependencies.c

Lines changed: 131 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,90 @@
3131
#include "distributed/worker_manager.h"
3232
#include "distributed/worker_transaction.h"
3333

34+
typedef enum RequiredObjectSet
35+
{
36+
REQUIRE_ONLY_DEPENDENCIES = 1,
37+
REQUIRE_OBJECT_AND_DEPENDENCIES = 2,
38+
} RequiredObjectSet;
39+
3440

3541
static void EnsureDependenciesCanBeDistributed(const ObjectAddress *relationAddress);
3642
static void ErrorIfCircularDependencyExists(const ObjectAddress *objectAddress);
3743
static int ObjectAddressComparator(const void *a, const void *b);
3844
static void EnsureDependenciesExistOnAllNodes(const ObjectAddress *target);
45+
static void EnsureRequiredObjectSetExistOnAllNodes(const ObjectAddress *target,
46+
RequiredObjectSet requiredObjectSet);
3947
static List * GetDependencyCreateDDLCommands(const ObjectAddress *dependency);
4048
static bool ShouldPropagateObject(const ObjectAddress *address);
4149
static char * DropTableIfExistsCommand(Oid relationId);
4250

4351
/*
44-
* EnsureDependenciesExistOnAllNodes finds all the dependencies that we support and makes
45-
* sure these are available on all nodes. If not available they will be created on the
46-
* nodes via a separate session that will be committed directly so that the objects are
47-
* visible to potentially multiple sessions creating the shards.
52+
* EnsureObjectAndDependenciesExistOnAllNodes is a wrapper around
53+
* EnsureRequiredObjectSetExistOnAllNodes to ensure the "object itself" (together
54+
* with its dependencies) is available on all nodes.
55+
*
56+
* Different than EnsureDependenciesExistOnAllNodes, we return early if the
57+
* target object is distributed already.
58+
*
59+
* The reason why we don't do the same in EnsureDependenciesExistOnAllNodes
60+
* is that it's is used when altering an object too and hence the target object
61+
* may instantly have a dependency that needs to be propagated now. For example,
62+
* when "⁠GRANT non_dist_role TO dist_role" is executed, we need to propagate
63+
* "non_dist_role" to all nodes before propagating the "GRANT" command itself.
64+
* For this reason, we call EnsureDependenciesExistOnAllNodes for "dist_role"
65+
* and it would automatically discover that "non_dist_role" is a dependency of
66+
* "dist_role" and propagate it beforehand.
67+
*
68+
* However, when we're requested to create the target object itself (and
69+
* implicitly its dependencies), we're sure that we're not altering the target
70+
* object itself, hence we can return early if the target object is already
71+
* distributed. This is the case, for example, when
72+
* "REASSIGN OWNED BY dist_role TO non_dist_role" is executed. In that case,
73+
* "non_dist_role" is not a dependency of "dist_role" but we want to distribute
74+
* "non_dist_role" beforehand and we call this function for "non_dist_role",
75+
* not for "dist_role".
76+
*
77+
* See EnsureRequiredObjectExistOnAllNodes to learn more about how this
78+
* function deals with an object created within the same transaction.
79+
*/
80+
void
81+
EnsureObjectAndDependenciesExistOnAllNodes(const ObjectAddress *target)
82+
{
83+
if (IsAnyObjectDistributed(list_make1((ObjectAddress *) target)))
84+
{
85+
return;
86+
}
87+
EnsureRequiredObjectSetExistOnAllNodes(target, REQUIRE_OBJECT_AND_DEPENDENCIES);
88+
}
89+
90+
91+
/*
92+
* EnsureDependenciesExistOnAllNodes is a wrapper around
93+
* EnsureRequiredObjectSetExistOnAllNodes to ensure "all dependencies" of given
94+
* object --but not the object itself-- are available on all nodes.
95+
*
96+
* See EnsureRequiredObjectSetExistOnAllNodes to learn more about how this
97+
* function deals with an object created within the same transaction.
98+
*/
99+
static void
100+
EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
101+
{
102+
EnsureRequiredObjectSetExistOnAllNodes(target, REQUIRE_ONLY_DEPENDENCIES);
103+
}
104+
105+
106+
/*
107+
* EnsureRequiredObjectSetExistOnAllNodes finds all the dependencies that we support and makes
108+
* sure these are available on all nodes if required object set is REQUIRE_ONLY_DEPENDENCIES.
109+
* Otherwise, i.e., if required object set is REQUIRE_OBJECT_AND_DEPENDENCIES, then this
110+
* function creates the object itself on all nodes too. This function ensures that each
111+
* of the dependencies are supported by Citus but doesn't check the same for the target
112+
* object itself (when REQUIRE_OBJECT_AND_DEPENDENCIES) is provided because we assume that
113+
* callers don't call this function for an unsupported function at all.
114+
*
115+
* If not available, they will be created on the nodes via a separate session that will be
116+
* committed directly so that the objects are visible to potentially multiple sessions creating
117+
* the shards.
48118
*
49119
* Note; only the actual objects are created via a separate session, the records to
50120
* pg_dist_object are created in this session. As a side effect the objects could be
@@ -55,29 +125,52 @@ static char * DropTableIfExistsCommand(Oid relationId);
55125
* postgres native CREATE IF NOT EXISTS, or citus helper functions.
56126
*/
57127
static void
58-
EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
128+
EnsureRequiredObjectSetExistOnAllNodes(const ObjectAddress *target,
129+
RequiredObjectSet requiredObjectSet)
59130
{
60-
List *dependenciesWithCommands = NIL;
131+
Assert(requiredObjectSet == REQUIRE_ONLY_DEPENDENCIES ||
132+
requiredObjectSet == REQUIRE_OBJECT_AND_DEPENDENCIES);
133+
134+
135+
List *objectsWithCommands = NIL;
61136
List *ddlCommands = NULL;
62137

63138
/*
64139
* If there is any unsupported dependency or circular dependency exists, Citus can
65140
* not ensure dependencies will exist on all nodes.
141+
*
142+
* Note that we don't check whether "target" is distributable (in case
143+
* REQUIRE_OBJECT_AND_DEPENDENCIES is provided) because we expect callers
144+
* to not even call this function if Citus doesn't know how to propagate
145+
* "target" object itself.
66146
*/
67147
EnsureDependenciesCanBeDistributed(target);
68148

69149
/* collect all dependencies in creation order and get their ddl commands */
70-
List *dependencies = GetDependenciesForObject(target);
71-
ObjectAddress *dependency = NULL;
72-
foreach_ptr(dependency, dependencies)
150+
List *objectsToBeCreated = GetDependenciesForObject(target);
151+
152+
/*
153+
* Append the target object to make sure that it's created after its
154+
* dependencies are created, if requested.
155+
*/
156+
if (requiredObjectSet == REQUIRE_OBJECT_AND_DEPENDENCIES)
73157
{
74-
List *dependencyCommands = GetDependencyCreateDDLCommands(dependency);
158+
ObjectAddress *targetCopy = palloc(sizeof(ObjectAddress));
159+
*targetCopy = *target;
160+
161+
objectsToBeCreated = lappend(objectsToBeCreated, targetCopy);
162+
}
163+
164+
ObjectAddress *object = NULL;
165+
foreach_ptr(object, objectsToBeCreated)
166+
{
167+
List *dependencyCommands = GetDependencyCreateDDLCommands(object);
75168
ddlCommands = list_concat(ddlCommands, dependencyCommands);
76169

77-
/* create a new list with dependencies that actually created commands */
170+
/* create a new list with objects that actually created commands */
78171
if (list_length(dependencyCommands) > 0)
79172
{
80-
dependenciesWithCommands = lappend(dependenciesWithCommands, dependency);
173+
objectsWithCommands = lappend(objectsWithCommands, object);
81174
}
82175
}
83176
if (list_length(ddlCommands) <= 0)
@@ -100,34 +193,47 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
100193
List *remoteNodeList = ActivePrimaryRemoteNodeList(RowShareLock);
101194

102195
/*
103-
* Lock dependent objects explicitly to make sure same DDL command won't be sent
196+
* Lock objects to be created explicitly to make sure same DDL command won't be sent
104197
* multiple times from parallel sessions.
105198
*
106-
* Sort dependencies that will be created on workers to not to have any deadlock
199+
* Sort the objects that will be created on workers to not to have any deadlock
107200
* issue if different sessions are creating different objects.
108201
*/
109-
List *addressSortedDependencies = SortList(dependenciesWithCommands,
202+
List *addressSortedDependencies = SortList(objectsWithCommands,
110203
ObjectAddressComparator);
111-
foreach_ptr(dependency, addressSortedDependencies)
204+
foreach_ptr(object, addressSortedDependencies)
112205
{
113-
LockDatabaseObject(dependency->classId, dependency->objectId,
114-
dependency->objectSubId, ExclusiveLock);
206+
LockDatabaseObject(object->classId, object->objectId,
207+
object->objectSubId, ExclusiveLock);
115208
}
116209

117210

118211
/*
119-
* We need to propagate dependencies via the current user's metadata connection if
120-
* any dependency for the target is created in the current transaction. Our assumption
121-
* is that if we rely on a dependency created in the current transaction, then the
122-
* current user, most probably, has permissions to create the target object as well.
212+
* We need to propagate objects via the current user's metadata connection if
213+
* any of the objects that we're interested in are created in the current transaction.
214+
* Our assumption is that if we rely on an object created in the current transaction,
215+
* then the current user, most probably, has permissions to create the target object
216+
* as well.
217+
*
123218
* Note that, user still may not be able to create the target due to no permissions
124219
* for any of its dependencies. But this is ok since it should be rare.
125220
*
126221
* If we opted to use a separate superuser connection for the target, then we would
127222
* have visibility issues since propagated dependencies would be invisible to
128223
* the separate connection until we locally commit.
129224
*/
130-
if (HasAnyDependencyInPropagatedObjects(target))
225+
List *createdObjectList = GetAllSupportedDependenciesForObject(target);
226+
227+
/* consider target as well if we're requested to create it too */
228+
if (requiredObjectSet == REQUIRE_OBJECT_AND_DEPENDENCIES)
229+
{
230+
ObjectAddress *targetCopy = palloc(sizeof(ObjectAddress));
231+
*targetCopy = *target;
232+
233+
createdObjectList = lappend(createdObjectList, targetCopy);
234+
}
235+
236+
if (HasAnyObjectInPropagatedObjects(createdObjectList))
131237
{
132238
SendCommandListToRemoteNodesWithMetadata(ddlCommands);
133239
}
@@ -150,7 +256,7 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
150256
* that objects have been created on remote nodes before marking them
151257
* distributed, so MarkObjectDistributed wouldn't fail.
152258
*/
153-
foreach_ptr(dependency, dependenciesWithCommands)
259+
foreach_ptr(object, objectsWithCommands)
154260
{
155261
/*
156262
* pg_dist_object entries must be propagated with the super user, since
@@ -160,7 +266,7 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
160266
* Only dependent object's metadata should be propagated with super user.
161267
* Metadata of the table itself must be propagated with the current user.
162268
*/
163-
MarkObjectDistributedViaSuperUser(dependency);
269+
MarkObjectDistributedViaSuperUser(object);
164270
}
165271
}
166272

src/backend/distributed/commands/distribute_object_ops.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,17 @@ static DistributeObjectOps Any_CreateRole = {
275275
.address = CreateRoleStmtObjectAddress,
276276
.markDistributed = true,
277277
};
278+
279+
static DistributeObjectOps Any_ReassignOwned = {
280+
.deparse = DeparseReassignOwnedStmt,
281+
.qualify = NULL,
282+
.preprocess = NULL,
283+
.postprocess = PostprocessReassignOwnedStmt,
284+
.operationType = DIST_OPS_ALTER,
285+
.address = NULL,
286+
.markDistributed = false,
287+
};
288+
278289
static DistributeObjectOps Any_DropOwned = {
279290
.deparse = DeparseDropOwnedStmt,
280291
.qualify = NULL,
@@ -1878,6 +1889,11 @@ GetDistributeObjectOps(Node *node)
18781889
return &Any_DropOwned;
18791890
}
18801891

1892+
case T_ReassignOwnedStmt:
1893+
{
1894+
return &Any_ReassignOwned;
1895+
}
1896+
18811897
case T_DropStmt:
18821898
{
18831899
DropStmt *stmt = castNode(DropStmt, node);

src/backend/distributed/commands/owned.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
#include "distributed/version_compat.h"
4949
#include "distributed/worker_transaction.h"
5050

51+
52+
static ObjectAddress * GetNewRoleAddress(ReassignOwnedStmt *stmt);
53+
5154
/*
5255
* PreprocessDropOwnedStmt finds the distributed role out of the ones
5356
* being dropped and unmarks them distributed and creates the drop statements
@@ -89,3 +92,81 @@ PreprocessDropOwnedStmt(Node *node, const char *queryString,
8992

9093
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
9194
}
95+
96+
97+
/*
98+
* PostprocessReassignOwnedStmt takes a Node pointer representing a REASSIGN
99+
* OWNED statement and performs any necessary post-processing after the statement
100+
* has been executed locally.
101+
*
102+
* We filter out local roles in OWNED BY clause before deparsing the command,
103+
* meaning that we skip reassigning what is owned by local roles. However,
104+
* if the role specified in TO clause is local, we automatically distribute
105+
* it before deparsing the command.
106+
*/
107+
List *
108+
PostprocessReassignOwnedStmt(Node *node, const char *queryString)
109+
{
110+
ReassignOwnedStmt *stmt = castNode(ReassignOwnedStmt, node);
111+
List *allReassignRoles = stmt->roles;
112+
113+
List *distributedReassignRoles = FilterDistributedRoles(allReassignRoles);
114+
115+
if (list_length(distributedReassignRoles) <= 0)
116+
{
117+
return NIL;
118+
}
119+
120+
if (!ShouldPropagate())
121+
{
122+
return NIL;
123+
}
124+
125+
EnsureCoordinator();
126+
127+
stmt->roles = distributedReassignRoles;
128+
char *sql = DeparseTreeNode((Node *) stmt);
129+
stmt->roles = allReassignRoles;
130+
131+
ObjectAddress *newRoleAddress = GetNewRoleAddress(stmt);
132+
133+
/*
134+
* We temporarily enable create / alter role propagation to properly
135+
* propagate the role specified in TO clause.
136+
*/
137+
int saveNestLevel = NewGUCNestLevel();
138+
set_config_option("citus.enable_create_role_propagation", "on",
139+
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
140+
GUC_ACTION_LOCAL, true, 0, false);
141+
set_config_option("citus.enable_alter_role_propagation", "on",
142+
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
143+
GUC_ACTION_LOCAL, true, 0, false);
144+
145+
set_config_option("citus.enable_alter_role_set_propagation", "on",
146+
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
147+
GUC_ACTION_LOCAL, true, 0, false);
148+
149+
EnsureObjectAndDependenciesExistOnAllNodes(newRoleAddress);
150+
151+
/* rollback GUCs to the state before this session */
152+
AtEOXact_GUC(true, saveNestLevel);
153+
154+
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
155+
sql,
156+
ENABLE_DDL_PROPAGATION);
157+
158+
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
159+
}
160+
161+
162+
/*
163+
* GetNewRoleAddress returns the ObjectAddress of the new role
164+
*/
165+
static ObjectAddress *
166+
GetNewRoleAddress(ReassignOwnedStmt *stmt)
167+
{
168+
Oid roleOid = get_role_oid(stmt->newrole->rolename, false);
169+
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
170+
ObjectAddressSet(*address, AuthIdRelationId, roleOid);
171+
return address;
172+
}

src/backend/distributed/deparser/deparse_owned_stmts.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ AppendRoleList(StringInfo buf, List *roleList)
7171
{
7272
Node *roleNode = (Node *) lfirst(cell);
7373
Assert(IsA(roleNode, RoleSpec) || IsA(roleNode, AccessPriv));
74-
char const *rolename = NULL;
74+
const char *rolename = NULL;
7575
if (IsA(roleNode, RoleSpec))
7676
{
7777
rolename = RoleSpecString((RoleSpec *) roleNode, true);
@@ -83,3 +83,27 @@ AppendRoleList(StringInfo buf, List *roleList)
8383
}
8484
}
8585
}
86+
87+
88+
static void
89+
AppendReassignOwnedStmt(StringInfo buf, ReassignOwnedStmt *stmt)
90+
{
91+
appendStringInfo(buf, "REASSIGN OWNED BY ");
92+
93+
AppendRoleList(buf, stmt->roles);
94+
const char *newRoleName = RoleSpecString(stmt->newrole, true);
95+
appendStringInfo(buf, " TO %s", newRoleName);
96+
}
97+
98+
99+
char *
100+
DeparseReassignOwnedStmt(Node *node)
101+
{
102+
ReassignOwnedStmt *stmt = castNode(ReassignOwnedStmt, node);
103+
StringInfoData buf = { 0 };
104+
initStringInfo(&buf);
105+
106+
AppendReassignOwnedStmt(&buf, stmt);
107+
108+
return buf.data;
109+
}

0 commit comments

Comments
 (0)