Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upGitHub is where the world builds software
Millions of developers and companies build, ship, and maintain their software on GitHub — the largest and most advanced development platform in the world.
Logical replication support #42
Conversation
Codecov Report
@@ Coverage Diff @@
## master #42 +/- ##
==========================================
- Coverage 97.01% 96.99% -0.02%
==========================================
Files 16 17 +1
Lines 1405 1529 +124
==========================================
+ Hits 1363 1483 +120
- Misses 42 46 +4
Continue to review full report at Codecov.
|
The code looks good, but there's a few things that definitely could be improved. |
@@ -836,7 +847,8 @@ def poll_query_until(self, | |||
expected=True, | |||
commit=True, | |||
raise_programming_error=True, | |||
raise_internal_error=True): | |||
raise_internal_error=True, | |||
zero_rows_is_ok=False): |
funbringer
Mar 19, 2018
Collaborator
Isn't zero_rows_is_ok=True
effectively equal to expected=None
?
Isn't zero_rows_is_ok=True
effectively equal to expected=None
?
zilder
Mar 22, 2018
Author
Collaborator
In Subscription.catchup()
we are expecting True and at the same time it may happen that there is no rows at the moment (until statistics collected).
In Subscription.catchup()
we are expecting True and at the same time it may happen that there is no rows at the moment (until statistics collected).
@@ -987,6 +1004,41 @@ def catchup(self, dbname=None, username=None): | |||
except Exception as e: | |||
raise_from(CatchUpException("Failed to catch up", poll_lsn), e) | |||
|
|||
def publish(self, | |||
pubname, |
funbringer
Mar 19, 2018
Collaborator
Maybe change pubname
to name
?
Maybe change pubname
to name
?
dbname: database name where objects or interest are located | ||
username: replication username | ||
""" | ||
return Publication(pubname, self, tables, dbname, username) |
funbringer
Mar 19, 2018
Collaborator
Could you please use keyword args instead of positional args?
Could you please use keyword args instead of positional args?
|
||
# connection info | ||
conninfo = ( | ||
u"dbname={} user={} host={} port={}" |
funbringer
Mar 19, 2018
Collaborator
I think we could extract common conninfo
-related code from _create_recovery_conf()
into a function and add it to utils.py
. What do you think?
I think we could extract common conninfo
-related code from _create_recovery_conf()
into a function and add it to utils.py
. What do you think?
query=query, | ||
dbname=self.pub.dbname, | ||
username=username or self.pub.username, | ||
max_attempts=60, |
funbringer
Mar 19, 2018
Collaborator
IMHO it's better to replace max_attempts=60
and zero_rows_is_ok=True
with kwargs.
IMHO it's better to replace max_attempts=60
and zero_rows_is_ok=True
with kwargs.
dbname=dbname, | ||
username=username) | ||
|
||
def close(self, dbname=None, username=None): |
funbringer
Mar 19, 2018
Collaborator
Shouldn't it be called drop
instead?
Shouldn't it be called drop
instead?
@@ -462,6 +532,10 @@ def test_poll_query_until(self): | |||
node.poll_query_until( | |||
query='create table def()', expected=None) # returns nothing | |||
|
|||
# check 0 rows equivalent to expected=None | |||
node.poll_query_until( | |||
query='select * from pg_class where true = false', expected=None) |
funbringer
Mar 26, 2018
Collaborator
pg_class
should be schema-qualified.
pg_class
should be schema-qualified.
zilder
May 31, 2018
Author
Collaborator
done
done
… the Sphinx templates and module header was added as well.
# create publication in database | ||
t = "table " + ", ".join(tables) if tables else "all tables" | ||
query = "create publication {} for {}" | ||
node.safe_psql(query.format(name, t), dbname=dbname, username=username) |
funbringer
Apr 4, 2018
Collaborator
All occurrences of safe_psql
should be replaced with execute
for better performance.
All occurrences of safe_psql
should be replaced with execute
for better performance.
zilder
May 31, 2018
Author
Collaborator
Done. However I had to refactor PostgresNode.execute()
method a little bit. Some statements (such as CREATE/DROP SUBSCRIPTION
) require non-transactional environment but execute
would start transaction automatically. So I used autocommit option which allows to run that kind of queries.
Done. However I had to refactor PostgresNode.execute()
method a little bit. Some statements (such as CREATE/DROP SUBSCRIPTION
) require non-transactional environment but execute
would start transaction automatically. So I used autocommit option which allows to run that kind of queries.
|
||
class Subscription(object): | ||
def __init__(self, | ||
name, |
funbringer
Apr 4, 2018
Collaborator
It's better to place name
right after the mandatory args. Chances are we'll come up with some name generation facility, but we won't be able to make name
optional without moving it, which is something I'd like to avoid.
It's better to place name
right after the mandatory args. Chances are we'll come up with some name generation facility, but we won't be able to make name
optional without moving it, which is something I'd like to avoid.
constructing subscription objects. | ||
Args: | ||
name: subscription name |
funbringer
Apr 4, 2018
Collaborator
I've also noticed that some doc strings end with commas while others don't.
I've also noticed that some doc strings end with commas while others don't.
zilder
May 31, 2018
Author
Collaborator
Fixed
Fixed
query=query, | ||
dbname=self.pub.dbname, | ||
username=username or self.pub.username, | ||
max_attempts=60) |
funbringer
Apr 4, 2018
Collaborator
Personally, I don't like the hard-coded number.
Personally, I don't like the hard-coded number.
ildus
Apr 4, 2018
Collaborator
I think it should be a parameter here.
I think it should be a parameter here.
zilder
May 31, 2018
Author
Collaborator
Ok, added LOGICAL_REPL_MAX_CATCHUP_ATTEMPTS
parameter
Ok, added LOGICAL_REPL_MAX_CATCHUP_ATTEMPTS
parameter
After that :meth:`~.PostgresNode.publish()` and :meth:`~.PostgresNode.subscribe()` | ||
methods may be used to setup replication. Example: | ||
>>> from .api import get_new_node |
ildus
Apr 4, 2018
Collaborator
This should be changed to from testgres import get_new_node
. Documentation should contain working code, even it would require remove doctest.
This should be changed to from testgres import get_new_node
. Documentation should contain working code, even it would require remove doctest.
zilder
May 31, 2018
Author
Collaborator
Agree
Agree
@@ -28,7 +28,7 @@ | |||
... replica.catchup() # wait until changes are visible | |||
... print(replica.execute('postgres', 'select count(*) from test')) | |||
PostgresNode(name='...', port=..., base_dir='...') | |||
[(3,)] | |||
[(3L,)] |
funbringer
May 31, 2018
Collaborator
is this necessary?
is this necessary?
zilder
Jun 1, 2018
Author
Collaborator
I think it was when I tested with doctest. But now when I ran doctest it showed [(3,)]
so I am confused :) Will change it back then as it shouldn't be part of this pull request anyway.
I think it was when I tested with doctest. But now when I ran doctest it showed [(3,)]
so I am confused :) Will change it back then as it shouldn't be part of this pull request anyway.
@@ -413,6 +417,7 @@ def default_conf(self, | |||
fsync=False, | |||
unix_sockets=True, | |||
allow_streaming=True, | |||
allow_logical=False, |
funbringer
May 31, 2018
Collaborator
Why don't we enable this by default?
Why don't we enable this by default?
zilder
Jun 1, 2018
Author
Collaborator
Because it is not supported on postgres versions below 10 and there is specific message when someone's trying to enable this feature on those versions. Besides it produces extra WAL data and hence could work slightly slower.
Because it is not supported on postgres versions below 10 and there is specific message when someone's trying to enable this feature on those versions. Besides it produces extra WAL data and hence could work slightly slower.
funbringer
Jun 1, 2018
Collaborator
Ah, i see.
Ah, i see.
""" | ||
Drop publication | ||
""" | ||
self.node.safe_psql( |
funbringer
May 31, 2018
Collaborator
Could we replace safe_psql
with execute
?
Could we replace safe_psql
with execute
?
zilder
Jun 1, 2018
Author
Collaborator
Yep, I overlooked it
Yep, I overlooked it
# check 0 columns | ||
with self.assertRaises(QueryException): | ||
node.poll_query_until(query='select from pg_class limit 1') | ||
node.poll_query_until( | ||
query='select from pg_catalog.pg_class limit 1') |
funbringer
May 31, 2018
Collaborator
Nice catch!
Nice catch!
The patch looks good! |
Added publication/subscription feature. Typical use case: