Propagate foreign server ops - #5468
Conversation
5a408f1 to
9bf6664
Compare
|
|
||
| case T_CreateFdwStmt: | ||
| { | ||
| return &Any_CreateFdw; |
There was a problem hiding this comment.
Since foreign data wrappers are almost always created by an extension and we explicitly do not want to propagate objects created by an extension, we could skip CREATE FOREIGN DATA WRAPPER.
There was a problem hiding this comment.
What if it's not created by an extension? Like the one we have in the regression tests:
CREATE FUNCTION fake_fdw_handler()
RETURNS fdw_handler
AS 'citus'
LANGUAGE C STRICT;
CREATE FOREIGN DATA WRAPPER fake_fdw HANDLER fake_fdw_handler;I know that it doesn't seem like something a user would do :)
So you're suggesting to skip those CREATE FDW statements because (almost) no one will try to create fdw's manually, right?
There was a problem hiding this comment.
The utility seems low and there is some cost (e.g. making sure we don't accidentally break anything, making sure deparsing logic does not have SQL injection bugs). I'm ok supporting it, but I'm also ok not supporting it.
| PreprocessCreateFdwStmt(Node *node, const char *queryString, | ||
| ProcessUtilityContext processUtilityContext) | ||
| { | ||
| EnsureCoordinator(); |
There was a problem hiding this comment.
EnsureCoordinator should be below ShouldPropagate, otherwise we block this command on workers altogether
There was a problem hiding this comment.
ah, makes sense
Codecov Report
@@ Coverage Diff @@
## master #5468 +/- ##
==========================================
+ Coverage 92.75% 92.77% +0.02%
==========================================
Files 215 217 +2
Lines 45294 45509 +215
==========================================
+ Hits 42012 42222 +210
- Misses 3282 3287 +5 |
| PreprocessAlterForeignServerStmt(Node *node, const char *queryString, | ||
| ProcessUtilityContext processUtilityContext) | ||
| { | ||
| if (!ShouldPropagate()) |
There was a problem hiding this comment.
We can use ShouldPropagateObject instead of calling ShouldPropagate and IsObjectDistributed separately, as it looks more clear to me for objects. It is same for ones below as well.
There was a problem hiding this comment.
makes sense, done
99c7d59 to
917903b
Compare
5ae5230 to
b13bf4f
Compare
| /* | ||
| * CreateForeignServerStmtObjectAddress finds the ObjectAddress for the server | ||
| * that is created by given CreateForeignServerStmt. If missingOk is false and if | ||
| * the statistics does not exist, then it errors out. |
There was a problem hiding this comment.
Stale statistics comment
There was a problem hiding this comment.
fixed, thanks
| AS 'citus' | ||
| LANGUAGE C STRICT; | ||
|
|
||
| set citus.enable_ddl_propagation to off; |
There was a problem hiding this comment.
Why do we disable ddl propagation here?
There was a problem hiding this comment.
We don't want to propagate the commands between set citus.enable_ddl_propagation to off; and set citus.enable_ddl_propagation to on;, because these objects are already created on the workers, in pg_regress_multi.pl. Because of that, we get an "already exists" error on the worker, if we don't disable ddl prop.
We also don't want to remove them since some other tests depend on these.
There was a problem hiding this comment.
We should probably remove from pg_regress_multi.pl and let this create
| static char * GetDefElemActionString(DefElemAction action); | ||
|
|
||
| char * | ||
| DeparseCreateForeignServerStmt(Node *node) |
There was a problem hiding this comment.
minor: needs comment
There was a problem hiding this comment.
Well, it seems we don't have much comments on these deparse functions, so I'm fine with not having here as well
| -- verify that the server is created on the worker | ||
| SELECT COUNT(*)=1 FROM pg_foreign_server WHERE srvname = 'foreign_server'; | ||
| \c - - - :master_port | ||
| ALTER SERVER foreign_server OPTIONS (ADD passfile 'to_be_dropped'); |
There was a problem hiding this comment.
can we have any quoted/escaped options and/or values?
| foreach_ptr(def, stmt->options) | ||
| { | ||
| const char *value = defGetString(def); | ||
| appendStringInfo(buf, "%s \'%s\'", def->defname, value); |
There was a problem hiding this comment.
similarly: should we call quote_literal_cstr for name/value?
There was a problem hiding this comment.
Defnames don't have quotes according to pg syntax.
Values have single quotes. I've added quote_identifier for that
| { | ||
| bool missingOk = false; | ||
| ObjectAddress typeAddress = GetObjectAddressFromParseTree(node, missingOk); | ||
| EnsureDependenciesExistOnAllNodes(&typeAddress); |
There was a problem hiding this comment.
What kind of dependency a foreign server might have? Do we have a test for that?
There was a problem hiding this comment.
So, I think a server can depend to an extension, and an extension can depend on a schema? So, such a test might be nice:
cREATE SCHEMa sc1;
CREATE EXTENSION postgres_fdw WITH SCHEMA sc1;
CREATE SERVER ....
-- make sure that schema is also distributedThere was a problem hiding this comment.
Added, thanks!
| CREATE SERVER fake_fdw_server FOREIGN DATA WRAPPER fake_fdw; | ||
| -- test propagating foreign server creation | ||
| CREATE EXTENSION postgres_fdw; | ||
| CREATE SERVER foreign_server TYPE 'test_type' VERSION 'v1' |
There was a problem hiding this comment.
I think we should have a very basic test with citus_add_node. Probably here is not the best place to add such a test, but can you find a place where we ensure that adding node sends the foreign server to the new node as well?
There was a problem hiding this comment.
I went over the test schedule but this file seemed like the most relevant place for this test. So I've added it to multi_create_fdw.sql.
If you have a suggestion I could consider moving it
| { | ||
| const char *value = defGetString(def); | ||
|
|
||
| appendStringInfo(buf, " \'%s\'", value); |
There was a problem hiding this comment.
Any time you see quotes in an appendStringInfo, assume that there's a SQL injection bug.
Quoting should always be done separately to take into account escaping.
There was a problem hiding this comment.
I've added that for CREATE SERVER OPTIONS: https://github.com/citusdata/citus/pull/5468/files#diff-c2f60db4cb9c4046fb711bd8ded1841ad0cede72d4ed8239aaf91e6151e9f90eR150-R151
But apparently missed that for ALTER SERVER.
Could you please check the one that is for CREATE SERVER? I'm considering applying the same here
df10a6a to
2c1ed64
Compare
Önder Kalacı (onderkalaci)
left a comment
There was a problem hiding this comment.
I'll continue reviewing & testing
| UnmarkObjectDistributed(address); | ||
| } | ||
|
|
||
| /* |
There was a problem hiding this comment.
This feels dangerous. Can't we create a new DropStmt with distributedServerNames?
In fact, I'm in favor of throwing an error if not all of them are distributed servers.
As we discussed earlier, these kinds of operations are dangerous for MX anyway.
| CreateForeignServerStmtObjectAddress(Node *node, bool missing_ok) | ||
| { | ||
| CreateForeignServerStmt *stmt = castNode(CreateForeignServerStmt, node); | ||
| ForeignServer *server = GetForeignServerByName(stmt->servername, missing_ok); |
There was a problem hiding this comment.
I think passing servername to this function can help to remove lots of repetition in the code, say we can use it in PreprocessRenameForeignServerStmt and others
There was a problem hiding this comment.
Don't we expect all object address functions to have this signature?
https://github.com/citusdata/citus/blob/master/src/backend/distributed/deparser/objectaddress.c#L36
There was a problem hiding this comment.
What I meant is move the following -- by passing servername as parameter:
ForeignServer *server = GetForeignServerByName(servername, missing_ok);
Oid serverOid = server->serverid;
ObjectAddress address = { 0 };
ObjectAddressSet(address, ForeignServerRelationId, serverOid);to a function, and use it in functions such as PreprocessRenameForeignServerStmt. But you need to palloc and return address*
| CreateForeignServerStmt *createStmt = makeNode(CreateForeignServerStmt); | ||
|
|
||
| /* set server name and if_not_exists fields */ | ||
| createStmt->servername = server->servername; |
There was a problem hiding this comment.
don't we need to createStmt->servername in this memory context? createStmt->servername = pstrdup(server->servername);
We should do same for all the pointers in the new CreateForeignServerStmt. As an example, see RecreateExtensionStmt
| SELECT srvowner FROM pg_foreign_server WHERE srvname = 'foreign''server_1!'; | ||
| srvowner | ||
| --------------------------------------------------------------------- | ||
| 3373 |
There was a problem hiding this comment.
can we join with pg_roles to print the rolname instead of oid? This applies to all role ids in the test
There was a problem hiding this comment.
something like:
SELECT rolname FROM pg_roles JOIN pg_foreign_server ON (pg_roles.oid=pg_foreign_server.srvowner) WHERE srvname = 'foreign_server';
rolname
-------------
onderkalaci
(1 row)
so that the tests do not rely on OIDs
| } | ||
|
|
||
| EnsureCoordinator(); | ||
|
|
There was a problem hiding this comment.
Do we need EnsureDependenciesExistOnAllNodes here? I think with AlterServer you cannot alter the server to depend on a new object, right? Such as ALTER SERVER .. SET SCHEMA new_schema or such? If so, we should be fine.
As far as I can read from the docs, ALTER SERVER can only change Server's options?
There was a problem hiding this comment.
It's allowed to alter a server's owner, name and options. I thought like we might need to ensure the new owner role exists on the workers?
There was a problem hiding this comment.
Ah, that's why we do it on PostProcess.. ?
| PreprocessAlterForeignServerOwnerStmt(Node *node, const char *queryString, | ||
| ProcessUtilityContext processUtilityContext) | ||
| { | ||
| AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); |
There was a problem hiding this comment.
One thing that is tricky in this PR is alter owner. What if the new owner role is not on the workers?
alter server myserver2 OWNER TO my_role;
ERROR: role "my_role" does not exist
CONTEXT: while executing command on localhost:9702
Time: 52.760 msI think that might be fine on the community, but on the enterprise we probably need EnsureDependencies call to make sure that the necessary objects(e.g., roles) are on the workers. So, we should still add EnsureDependenciesExistOnAllNodes here.
And, on the enteprise specific tests, we should add something like
-- prevent creating the roles just yet, let ALTER SERVER do that
SET citus.enable_object_propagation TO off;
create role ... ;
SET citus.enable_object_propagation TO on;
ALTER SERVER .. SET OWNER ...;
.. check owner is set properly on the workers ..There was a problem hiding this comment.
I think we have the code and the same test for schemas, so probably fine not to add a specific test on this.
After getting this to enterprise, can you manually verify that it works expected (e.g., we send the roles)?
5a895ee to
0fab75e
Compare
| return; | ||
| } | ||
|
|
||
| appendStringInfoString(buf, "OPTIONS ("); |
There was a problem hiding this comment.
you might be able to use AppendOptionListToString here
66c8eb0 to
818579c
Compare
Önder Kalacı (onderkalaci)
left a comment
There was a problem hiding this comment.
I think this is almost ready to merge, I had few minor questions
| SELECT srvowner FROM pg_foreign_server WHERE srvname = 'foreign''server_1!'; | ||
| srvowner | ||
| --------------------------------------------------------------------- | ||
| 3373 |
There was a problem hiding this comment.
something like:
SELECT rolname FROM pg_roles JOIN pg_foreign_server ON (pg_roles.oid=pg_foreign_server.srvowner) WHERE srvname = 'foreign_server';
rolname
-------------
onderkalaci
(1 row)
so that the tests do not rely on OIDs
| PreprocessAlterForeignServerOwnerStmt(Node *node, const char *queryString, | ||
| ProcessUtilityContext processUtilityContext) | ||
| { | ||
| AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); |
There was a problem hiding this comment.
I think we have the code and the same test for schemas, so probably fine not to add a specific test on this.
After getting this to enterprise, can you manually verify that it works expected (e.g., we send the roles)?
| foreach_ptr(serverValue, serverNames) | ||
| { | ||
| char *serverNameString = strVal(serverValue); | ||
| ForeignServer *server = GetForeignServerByName(serverNameString, false); |
There was a problem hiding this comment.
can we use GetObjectAddressByServerName?
| Assert(stmt->renameType == OBJECT_FOREIGN_SERVER); | ||
|
|
||
| char *serverName = strVal(stmt->object); | ||
| ForeignServer *server = GetForeignServerByName(serverName, false); |
There was a problem hiding this comment.
can we use GetObjectAddressByServerName?
| GetForeignServerCreateDDLCommand(Oid serverId) | ||
| { | ||
| /* generate a statement for creation of the server in "if not exists" construct */ | ||
| Node *stmt = RecreateForeignServerStmt(serverId); |
There was a problem hiding this comment.
I really couldn't follow why we only have RecreateForeignServerStmt but say we don't have ReCreateDropForeignServerStmt or ReCreateAlterForeignServerStmt.
What makes RecreateForeignServerStmt special?
If you copy & pasted the logic from somewhere else, can you ask the original author why that is the case?
There was a problem hiding this comment.
This function is getting used to create the server on a worker when, say, we are doing a citus_add_node.
Here we generate a CreateForeignServerStmt with if_not_exists = true, then deparse it and send the command to workers.
fc2819b to
dc36a4c
Compare
| static char * GetDefElemActionString(DefElemAction action); | ||
|
|
||
| char * | ||
| DeparseCreateForeignServerStmt(Node *node) |
There was a problem hiding this comment.
Well, it seems we don't have much comments on these deparse functions, so I'm fine with not having here as well
| FOREIGN DATA WRAPPER postgres_fdw | ||
| OPTIONS (host 'testhost', port '5432', dbname 'testdb'); | ||
|
|
||
| \c - - - :master_port |
There was a problem hiding this comment.
I think we should not leave any objects behind. So, maybe recreate test_dependent_schema and make sure once we drop, no objects left behind.
In general, left over objects can become painful once you do large refactors etc.
dc36a4c to
042d45b
Compare
DESCRIPTION: Adds propagation for foreign server commands