Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit e344010

Browse files
committed
Fix port script to handle foreign key constraints
Fixes #8652.
1 parent c059413 commit e344010

File tree

3 files changed

+61
-6
lines changed

3 files changed

+61
-6
lines changed

.buildkite/test_db.db

16 KB
Binary file not shown.

changelog.d/8730.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix port script to correctly handle foreign key constraints. Broke in v1.21.0.

scripts/synapse_port_db

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import logging
2222
import sys
2323
import time
2424
import traceback
25-
from typing import Optional
25+
from typing import Dict, Optional, Set
2626

2727
import yaml
2828

@@ -292,6 +292,34 @@ class Porter(object):
292292

293293
return table, already_ported, total_to_port, forward_chunk, backward_chunk
294294

295+
async def get_table_constraints(self) -> Dict[str, Set[str]]:
296+
"""Returns a map of tables that have foreign key constraints to tables they depend on.
297+
"""
298+
299+
def _get_constraints(txn):
300+
# We can pull the information about foreign key constraints out from
301+
# the postgres schema tables.
302+
sql = """
303+
SELECT DISTINCT
304+
tc.table_name,
305+
ccu.table_name AS foreign_table_name
306+
FROM
307+
information_schema.table_constraints AS tc
308+
INNER JOIN information_schema.constraint_column_usage AS ccu
309+
USING (table_schema, constraint_name)
310+
WHERE tc.constraint_type = 'FOREIGN KEY';
311+
"""
312+
txn.execute(sql)
313+
314+
results = {}
315+
for table, foreign_table in txn:
316+
results.setdefault(table, set()).add(foreign_table)
317+
return results
318+
319+
return await self.postgres_store.db_pool.runInteraction(
320+
"get_table_constraints", _get_constraints
321+
)
322+
295323
async def handle_table(
296324
self, table, postgres_size, table_size, forward_chunk, backward_chunk
297325
):
@@ -619,15 +647,41 @@ class Porter(object):
619647
consumeErrors=True,
620648
)
621649
)
650+
tables_to_port_info_map = {r[0]: r[1:] for r in setup_res}
622651

623652
# Step 4. Do the copying.
653+
#
654+
# This is slightly convoluted as we need to ensure tables are ported
655+
# in the correct order due to foreign key constraints.
624656
self.progress.set_state("Copying to postgres")
625-
await make_deferred_yieldable(
626-
defer.gatherResults(
627-
[run_in_background(self.handle_table, *res) for res in setup_res],
628-
consumeErrors=True,
657+
658+
constraints = await self.get_table_constraints()
659+
tables_ported = set() # type: Set[str]
660+
661+
while tables_to_port_info_map:
662+
# Pulls out all tables that are still to be ported and which
663+
# only depend on tables that are already ported (if any).
664+
tables_to_port = [
665+
table
666+
for table in tables_to_port_info_map
667+
if not constraints.get(table, set()) - tables_ported
668+
]
669+
670+
await make_deferred_yieldable(
671+
defer.gatherResults(
672+
[
673+
run_in_background(
674+
self.handle_table,
675+
table,
676+
*tables_to_port_info_map.pop(table),
677+
)
678+
for table in tables_to_port
679+
],
680+
consumeErrors=True,
681+
)
629682
)
630-
)
683+
684+
tables_ported.update(tables_to_port)
631685

632686
# Step 5. Set up sequences
633687
self.progress.set_state("Setting up sequence generators")

0 commit comments

Comments
 (0)