pgbelt 0.7.0__tar.gz → 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pgbelt-0.7.0 → pgbelt-0.7.2}/PKG-INFO +1 -1
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/setup.py +25 -2
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/status.py +8 -1
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/sync.py +17 -2
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/pglogical.py +2 -1
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/postgres.py +70 -16
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pyproject.toml +4 -4
- {pgbelt-0.7.0 → pgbelt-0.7.2}/LICENSE +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/README.md +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/__init__.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/__init__.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/convenience.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/helpers.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/login.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/preflight.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/schema.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/teardown.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/config/__init__.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/config/config.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/config/models.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/config/remote.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/main.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/__init__.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/asyncfuncs.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/dump.py +0 -0
- {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/logs.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pgbelt
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: A CLI tool used to manage Postgres data migrations from beginning to end, for a single database or a fleet, leveraging pglogical replication.
|
|
5
5
|
Author: Varjitt Jeeva
|
|
6
6
|
Author-email: varjitt.jeeva@autodesk.com
|
|
@@ -41,7 +41,23 @@ async def _setup_src_node(
|
|
|
41
41
|
|
|
42
42
|
pglogical_tables = pkey_tables
|
|
43
43
|
if conf.tables:
|
|
44
|
-
pglogical_tables = [
|
|
44
|
+
pglogical_tables = [
|
|
45
|
+
t
|
|
46
|
+
for t in pkey_tables
|
|
47
|
+
if t
|
|
48
|
+
in list(
|
|
49
|
+
map(str.lower, conf.tables)
|
|
50
|
+
) # Postgres returns table names in lowercase (in analyze_table_pkeys)
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# Intentionally throw an error if no tables are found, so that the user can correct their config.
|
|
54
|
+
# When reported by a certain user, errors showed when running the status command, but it was ignored,
|
|
55
|
+
# then the user ran setup and since that DIDN'T throw an error, they assumed everything was fine.
|
|
56
|
+
|
|
57
|
+
if not pglogical_tables:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"No tables were targeted to replicate. Please check your config's schema and tables. DB: {conf.db} DC: {conf.dc}, SCHEMA: {conf.schema_name} TABLES: {conf.tables}.\nIf TABLES is [], all tables in the schema should be replicated, but pgbelt still found no tables.\nCheck the schema name or reach out to the pgbelt team for help."
|
|
60
|
+
)
|
|
45
61
|
|
|
46
62
|
await configure_replication_set(
|
|
47
63
|
src_root_pool, pglogical_tables, conf.schema_name, src_logger
|
|
@@ -145,7 +161,14 @@ async def setup_back_replication(config_future: Awaitable[DbupgradeConfig]) -> N
|
|
|
145
161
|
|
|
146
162
|
pglogical_tables = pkeys
|
|
147
163
|
if conf.tables:
|
|
148
|
-
pglogical_tables = [
|
|
164
|
+
pglogical_tables = [
|
|
165
|
+
t
|
|
166
|
+
for t in pkeys
|
|
167
|
+
if t
|
|
168
|
+
in list(
|
|
169
|
+
map(str.lower, conf.tables)
|
|
170
|
+
) # Postgres returns table names in lowercase (in analyze_table_pkeys)
|
|
171
|
+
]
|
|
149
172
|
|
|
150
173
|
await configure_replication_set(
|
|
151
174
|
dst_root_pool, pglogical_tables, conf.schema_name, dst_logger
|
|
@@ -92,7 +92,14 @@ async def status(conf_future: Awaitable[DbupgradeConfig]) -> dict[str, str]:
|
|
|
92
92
|
all_tables = pkey_tables + non_pkey_tables
|
|
93
93
|
target_tables = all_tables
|
|
94
94
|
if conf.tables:
|
|
95
|
-
target_tables = [
|
|
95
|
+
target_tables = [
|
|
96
|
+
t
|
|
97
|
+
for t in all_tables
|
|
98
|
+
if t
|
|
99
|
+
in list(
|
|
100
|
+
map(str.lower, conf.tables)
|
|
101
|
+
) # Postgres gave us lowercase table names in analyze_table_pkeys
|
|
102
|
+
]
|
|
96
103
|
|
|
97
104
|
if not target_tables:
|
|
98
105
|
raise ValueError(
|
|
@@ -28,6 +28,7 @@ async def _sync_sequences(
|
|
|
28
28
|
src_logger: Logger,
|
|
29
29
|
dst_logger: Logger,
|
|
30
30
|
) -> None:
|
|
31
|
+
|
|
31
32
|
seq_vals = await dump_sequences(src_pool, targeted_sequences, schema, src_logger)
|
|
32
33
|
await load_sequences(dst_pool, seq_vals, dst_logger)
|
|
33
34
|
|
|
@@ -76,7 +77,14 @@ async def dump_tables(
|
|
|
76
77
|
_, tables, _ = await analyze_table_pkeys(src_pool, conf.schema_name, logger)
|
|
77
78
|
|
|
78
79
|
if conf.tables:
|
|
79
|
-
tables = [
|
|
80
|
+
tables = [
|
|
81
|
+
t
|
|
82
|
+
for t in tables
|
|
83
|
+
if t
|
|
84
|
+
in list(
|
|
85
|
+
map(str.lower, conf.tables)
|
|
86
|
+
) # Postgres returns table names in lowercase (in analyze_table_pkeys)
|
|
87
|
+
]
|
|
80
88
|
|
|
81
89
|
await dump_source_tables(conf, tables, logger)
|
|
82
90
|
|
|
@@ -184,7 +192,14 @@ async def _dump_and_load_all_tables(
|
|
|
184
192
|
) -> None:
|
|
185
193
|
_, tables, _ = await analyze_table_pkeys(src_pool, conf.schema_name, src_logger)
|
|
186
194
|
if conf.tables:
|
|
187
|
-
tables = [
|
|
195
|
+
tables = [
|
|
196
|
+
t
|
|
197
|
+
for t in tables
|
|
198
|
+
if t
|
|
199
|
+
in list(
|
|
200
|
+
map(str.lower, conf.tables)
|
|
201
|
+
) # Postgres returns table names in lowercase (in analyze_table_pkeys)
|
|
202
|
+
]
|
|
188
203
|
await dump_source_tables(conf, tables, src_logger)
|
|
189
204
|
await load_dumped_tables(conf, tables, dst_logger)
|
|
190
205
|
|
|
@@ -78,8 +78,9 @@ async def grant_pgl(pool: Pool, tables: list[str], schema: str, logger: Logger)
|
|
|
78
78
|
async with pool.acquire() as conn:
|
|
79
79
|
async with conn.transaction():
|
|
80
80
|
if tables:
|
|
81
|
+
tables_with_schema = [f"{schema}.{table}" for table in tables]
|
|
81
82
|
await conn.execute(
|
|
82
|
-
f"GRANT ALL ON TABLE {','.join(
|
|
83
|
+
f"GRANT ALL ON TABLE {','.join(tables_with_schema)} TO pglogical;"
|
|
83
84
|
)
|
|
84
85
|
else:
|
|
85
86
|
await conn.execute(
|
|
@@ -12,20 +12,38 @@ async def dump_sequences(
|
|
|
12
12
|
return a dictionary of sequence names mapped to their last values
|
|
13
13
|
"""
|
|
14
14
|
logger.info("Dumping sequence values...")
|
|
15
|
-
|
|
15
|
+
# Get all sequences in the schema
|
|
16
|
+
seqs = await pool.fetch(
|
|
17
|
+
f"""
|
|
18
|
+
SELECT '{schema}' || '.' || sequence_name
|
|
19
|
+
FROM information_schema.sequences
|
|
20
|
+
WHERE sequence_schema = '{schema}';
|
|
21
|
+
"""
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Note: When in an exodus migration with a non-public schema, the sequence names must be prefixed with the schema name.
|
|
25
|
+
# This may not be done by the user, so we must do it here.
|
|
26
|
+
proper_sequence_names = None
|
|
27
|
+
if targeted_sequences is not None:
|
|
28
|
+
proper_sequence_names = []
|
|
29
|
+
for seq in targeted_sequences:
|
|
30
|
+
if f"{schema}." not in seq:
|
|
31
|
+
proper_sequence_names.append(f"{schema}.{seq}")
|
|
32
|
+
else:
|
|
33
|
+
proper_sequence_names.append(seq)
|
|
34
|
+
targeted_sequences = proper_sequence_names
|
|
16
35
|
|
|
17
36
|
seq_vals = {}
|
|
37
|
+
final_seqs = []
|
|
38
|
+
# If we get a list of targeted sequences, we only want to dump whichever of those are found in the database and schema.
|
|
18
39
|
if targeted_sequences:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
seq_vals[f"{schema}.{seq_stripped}"] = await pool.fetchval(
|
|
27
|
-
f"SELECT last_value FROM {schema}.{seq};"
|
|
28
|
-
)
|
|
40
|
+
final_seqs = [r[0] for r in seqs if r[0] in targeted_sequences]
|
|
41
|
+
else: # Otherwise, we want to dump all sequences found in the schema.
|
|
42
|
+
final_seqs = [r[0] for r in seqs]
|
|
43
|
+
|
|
44
|
+
for seq in final_seqs:
|
|
45
|
+
res = await pool.fetchval(f"SELECT last_value FROM {seq};")
|
|
46
|
+
seq_vals[seq.strip()] = res
|
|
29
47
|
|
|
30
48
|
logger.debug(f"Dumped sequences: {seq_vals}")
|
|
31
49
|
return seq_vals
|
|
@@ -79,10 +97,16 @@ async def compare_data(
|
|
|
79
97
|
dst_old_extra_float_digits = await dst_pool.fetchval("SHOW extra_float_digits;")
|
|
80
98
|
await dst_pool.execute("SET extra_float_digits TO 0;")
|
|
81
99
|
|
|
100
|
+
has_run = False
|
|
82
101
|
for table in set(pkeys):
|
|
83
|
-
# If specific table list is defined and iterated table is not in that list, skip.
|
|
84
|
-
|
|
102
|
+
# If specific table list is defined and the iterated table is not in that list, skip.
|
|
103
|
+
# Note that the pkeys tables returned from Postgres are all lowercased, so we need to
|
|
104
|
+
# map the passed conf tables to lowercase.
|
|
105
|
+
if tables and (table not in list(map(str.lower, tables))):
|
|
85
106
|
continue
|
|
107
|
+
|
|
108
|
+
has_run = True # If this runs, we have at least one table to compare. We will use this flag to throw an error if no tables are found.
|
|
109
|
+
|
|
86
110
|
full_table_name = f"{schema}.{table}"
|
|
87
111
|
|
|
88
112
|
logger.debug(f"Validating table {full_table_name}...")
|
|
@@ -153,6 +177,13 @@ async def compare_data(
|
|
|
153
177
|
f"Dest Row: {dst_row}"
|
|
154
178
|
)
|
|
155
179
|
|
|
180
|
+
# Just a paranoia check. If this throws, then it's possible pgbelt didn't migrate any data.
|
|
181
|
+
# This was found in issue #420, and previous commands threw errors before this issue could arise.
|
|
182
|
+
if not has_run:
|
|
183
|
+
raise ValueError(
|
|
184
|
+
"No tables were found to compare. Please reach out to the pgbelt for help, and check if your data was migrated."
|
|
185
|
+
)
|
|
186
|
+
|
|
156
187
|
await src_pool.execute(f"SET extra_float_digits TO {src_old_extra_float_digits};")
|
|
157
188
|
await dst_pool.execute(f"SET extra_float_digits TO {dst_old_extra_float_digits};")
|
|
158
189
|
logger.info(
|
|
@@ -354,9 +385,22 @@ async def precheck_info(
|
|
|
354
385
|
AND n.nspname <> 'pglogical'
|
|
355
386
|
ORDER BY 1,2;"""
|
|
356
387
|
)
|
|
388
|
+
|
|
357
389
|
# We filter the table list if the user has specified a list of tables to target.
|
|
390
|
+
# Note, from issue #420, the above query will return the table names in lowercase,
|
|
391
|
+
# so we need to map the target_tables to lowercase.
|
|
358
392
|
if target_tables:
|
|
359
|
-
|
|
393
|
+
|
|
394
|
+
result["tables"] = [
|
|
395
|
+
t
|
|
396
|
+
for t in result["tables"]
|
|
397
|
+
if t["Name"] in list(map(str.lower, target_tables))
|
|
398
|
+
]
|
|
399
|
+
|
|
400
|
+
# We will not recapitalize the table names in the result["tables"] list,
|
|
401
|
+
# to preserve how Postgres sees those tables in its system catalog. Easy
|
|
402
|
+
# rabbit hole later if we keep patching the table names to match the user's
|
|
403
|
+
# input.
|
|
360
404
|
|
|
361
405
|
result["sequences"] = await pool.fetch(
|
|
362
406
|
"""
|
|
@@ -374,12 +418,22 @@ async def precheck_info(
|
|
|
374
418
|
ORDER BY 1,2;"""
|
|
375
419
|
)
|
|
376
420
|
|
|
377
|
-
# We filter the
|
|
421
|
+
# We filter the table list if the user has specified a list of tables to target.
|
|
422
|
+
# Note, from issue #420, the above query will return the table names in lowercase,
|
|
423
|
+
# so we need to map the target_tables to lowercase.
|
|
378
424
|
if target_sequences:
|
|
425
|
+
|
|
379
426
|
result["sequences"] = [
|
|
380
|
-
|
|
427
|
+
t
|
|
428
|
+
for t in result["sequences"]
|
|
429
|
+
if t["Name"] in list(map(str.lower, target_sequences))
|
|
381
430
|
]
|
|
382
431
|
|
|
432
|
+
# We will not recapitalize the table names in the result["tables"] list,
|
|
433
|
+
# to preserve how Postgres sees those tables in its system catalog. Easy
|
|
434
|
+
# rabbit hole later if we keep patching the table names to match the user's
|
|
435
|
+
# input.
|
|
436
|
+
|
|
383
437
|
users = await pool.fetch(
|
|
384
438
|
f"""
|
|
385
439
|
SELECT r.rolname, r.rolsuper, r.rolinherit,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pgbelt"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.2"
|
|
4
4
|
description = "A CLI tool used to manage Postgres data migrations from beginning to end, for a single database or a fleet, leveraging pglogical replication."
|
|
5
5
|
authors = ["Varjitt Jeeva <varjitt.jeeva@autodesk.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -18,14 +18,14 @@ tabulate = "^0.9.0"
|
|
|
18
18
|
typer = "^0.9.0"
|
|
19
19
|
|
|
20
20
|
[tool.poetry.dev-dependencies]
|
|
21
|
-
black = "~24.
|
|
21
|
+
black = "~24.3.0"
|
|
22
22
|
pre-commit = "~3.6.2"
|
|
23
23
|
flake8 = "^7.0.0"
|
|
24
24
|
pytest-cov = "~4.1.0"
|
|
25
|
-
pytest = "^8.
|
|
25
|
+
pytest = "^8.1.1"
|
|
26
26
|
coverage = {extras = ["toml"], version = "^7.4"}
|
|
27
27
|
safety = "^2.3.1"
|
|
28
|
-
mypy = "^1.
|
|
28
|
+
mypy = "^1.9"
|
|
29
29
|
xdoctest = {extras = ["colors"], version = "^1.1.3"}
|
|
30
30
|
flake8-bandit = "~4.1.1"
|
|
31
31
|
flake8-bugbear = ">=21.9.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|