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.
Files changed (26) hide show
  1. {pgbelt-0.7.0 → pgbelt-0.7.2}/PKG-INFO +1 -1
  2. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/setup.py +25 -2
  3. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/status.py +8 -1
  4. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/sync.py +17 -2
  5. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/pglogical.py +2 -1
  6. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/postgres.py +70 -16
  7. {pgbelt-0.7.0 → pgbelt-0.7.2}/pyproject.toml +4 -4
  8. {pgbelt-0.7.0 → pgbelt-0.7.2}/LICENSE +0 -0
  9. {pgbelt-0.7.0 → pgbelt-0.7.2}/README.md +0 -0
  10. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/__init__.py +0 -0
  11. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/__init__.py +0 -0
  12. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/convenience.py +0 -0
  13. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/helpers.py +0 -0
  14. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/login.py +0 -0
  15. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/preflight.py +0 -0
  16. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/schema.py +0 -0
  17. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/cmd/teardown.py +0 -0
  18. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/config/__init__.py +0 -0
  19. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/config/config.py +0 -0
  20. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/config/models.py +0 -0
  21. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/config/remote.py +0 -0
  22. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/main.py +0 -0
  23. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/__init__.py +0 -0
  24. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/asyncfuncs.py +0 -0
  25. {pgbelt-0.7.0 → pgbelt-0.7.2}/pgbelt/util/dump.py +0 -0
  26. {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.0
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 = [t for t in pkey_tables if t in conf.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 = [t for t in pkeys if t in conf.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 = [t for t in all_tables if t in conf.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 = [t for t in tables if t in conf.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 = [t for t in tables if t in conf.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(tables)} TO pglogical;"
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
- seqs = await pool.fetch("SELECT sequence_name FROM information_schema.sequences;")
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
- for seq in [r[0] for r in seqs if r[0] in targeted_sequences]:
20
- seq_vals[seq.strip()] = await pool.fetchval(
21
- f"SELECT last_value FROM {schema}.{seq};"
22
- )
23
- else:
24
- for seq in [r[0] for r in seqs]:
25
- seq_stripped = seq.strip()
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
- if tables and (table not in tables):
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
- result["tables"] = [t for t in result["tables"] if t["Name"] in target_tables]
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 sequence list if the user has specified a list of sequences to target.
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
- s for s in result["sequences"] if s["Name"] in target_sequences
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.0"
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.2.0"
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.0.2"
25
+ pytest = "^8.1.1"
26
26
  coverage = {extras = ["toml"], version = "^7.4"}
27
27
  safety = "^2.3.1"
28
- mypy = "^1.8"
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