31
31
#include "distributed/worker_manager.h"
32
32
#include "distributed/worker_transaction.h"
33
33
34
+ typedef enum RequiredObjectSet
35
+ {
36
+ REQUIRE_ONLY_DEPENDENCIES = 1 ,
37
+ REQUIRE_OBJECT_AND_DEPENDENCIES = 2 ,
38
+ } RequiredObjectSet ;
39
+
34
40
35
41
static void EnsureDependenciesCanBeDistributed (const ObjectAddress * relationAddress );
36
42
static void ErrorIfCircularDependencyExists (const ObjectAddress * objectAddress );
37
43
static int ObjectAddressComparator (const void * a , const void * b );
38
44
static void EnsureDependenciesExistOnAllNodes (const ObjectAddress * target );
45
+ static void EnsureRequiredObjectSetExistOnAllNodes (const ObjectAddress * target ,
46
+ RequiredObjectSet requiredObjectSet );
39
47
static List * GetDependencyCreateDDLCommands (const ObjectAddress * dependency );
40
48
static bool ShouldPropagateObject (const ObjectAddress * address );
41
49
static char * DropTableIfExistsCommand (Oid relationId );
42
50
43
51
/*
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.
48
118
*
49
119
* Note; only the actual objects are created via a separate session, the records to
50
120
* 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);
55
125
* postgres native CREATE IF NOT EXISTS, or citus helper functions.
56
126
*/
57
127
static void
58
- EnsureDependenciesExistOnAllNodes (const ObjectAddress * target )
128
+ EnsureRequiredObjectSetExistOnAllNodes (const ObjectAddress * target ,
129
+ RequiredObjectSet requiredObjectSet )
59
130
{
60
- List * dependenciesWithCommands = NIL ;
131
+ Assert (requiredObjectSet == REQUIRE_ONLY_DEPENDENCIES ||
132
+ requiredObjectSet == REQUIRE_OBJECT_AND_DEPENDENCIES );
133
+
134
+
135
+ List * objectsWithCommands = NIL ;
61
136
List * ddlCommands = NULL ;
62
137
63
138
/*
64
139
* If there is any unsupported dependency or circular dependency exists, Citus can
65
140
* 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.
66
146
*/
67
147
EnsureDependenciesCanBeDistributed (target );
68
148
69
149
/* 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 )
73
157
{
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 );
75
168
ddlCommands = list_concat (ddlCommands , dependencyCommands );
76
169
77
- /* create a new list with dependencies that actually created commands */
170
+ /* create a new list with objects that actually created commands */
78
171
if (list_length (dependencyCommands ) > 0 )
79
172
{
80
- dependenciesWithCommands = lappend (dependenciesWithCommands , dependency );
173
+ objectsWithCommands = lappend (objectsWithCommands , object );
81
174
}
82
175
}
83
176
if (list_length (ddlCommands ) <= 0 )
@@ -100,34 +193,47 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
100
193
List * remoteNodeList = ActivePrimaryRemoteNodeList (RowShareLock );
101
194
102
195
/*
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
104
197
* multiple times from parallel sessions.
105
198
*
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
107
200
* issue if different sessions are creating different objects.
108
201
*/
109
- List * addressSortedDependencies = SortList (dependenciesWithCommands ,
202
+ List * addressSortedDependencies = SortList (objectsWithCommands ,
110
203
ObjectAddressComparator );
111
- foreach_ptr (dependency , addressSortedDependencies )
204
+ foreach_ptr (object , addressSortedDependencies )
112
205
{
113
- LockDatabaseObject (dependency -> classId , dependency -> objectId ,
114
- dependency -> objectSubId , ExclusiveLock );
206
+ LockDatabaseObject (object -> classId , object -> objectId ,
207
+ object -> objectSubId , ExclusiveLock );
115
208
}
116
209
117
210
118
211
/*
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
+ *
123
218
* Note that, user still may not be able to create the target due to no permissions
124
219
* for any of its dependencies. But this is ok since it should be rare.
125
220
*
126
221
* If we opted to use a separate superuser connection for the target, then we would
127
222
* have visibility issues since propagated dependencies would be invisible to
128
223
* the separate connection until we locally commit.
129
224
*/
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 ))
131
237
{
132
238
SendCommandListToRemoteNodesWithMetadata (ddlCommands );
133
239
}
@@ -150,7 +256,7 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
150
256
* that objects have been created on remote nodes before marking them
151
257
* distributed, so MarkObjectDistributed wouldn't fail.
152
258
*/
153
- foreach_ptr (dependency , dependenciesWithCommands )
259
+ foreach_ptr (object , objectsWithCommands )
154
260
{
155
261
/*
156
262
* pg_dist_object entries must be propagated with the super user, since
@@ -160,7 +266,7 @@ EnsureDependenciesExistOnAllNodes(const ObjectAddress *target)
160
266
* Only dependent object's metadata should be propagated with super user.
161
267
* Metadata of the table itself must be propagated with the current user.
162
268
*/
163
- MarkObjectDistributedViaSuperUser (dependency );
269
+ MarkObjectDistributedViaSuperUser (object );
164
270
}
165
271
}
166
272
0 commit comments