Skip to content

Commit 6944ec2

Browse files
committed
Expand FK investigation with detection and fixing analysis
Additional findings: - Third case that breaks FKs: changing PK away from referenced column (SQLite requires FK targets to be PRIMARY KEY or UNIQUE) - Detection is easy: iterate tables, find incoming FKs, check if referenced columns are being renamed/dropped/losing uniqueness - Automatic fixing for renames is moderate complexity: transform referencing tables first to update their FK constraints - Challenges include circular references and transaction safety - Proposed API: update_incoming_fks=True flag or better error messages
1 parent ffe2213 commit 6944ec2

File tree

1 file changed

+72
-0
lines changed

1 file changed

+72
-0
lines changed

TRANSFORM_FK_INVESTIGATION.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,25 @@ The `transform()` method (db.py lines 1853-1917) implements the following safety
9595
| Rename non-referenced column | Works, FKs intact | Works, FKs intact |
9696
| Rename referenced column | FAILS (rollback) | Works, FKs BROKEN! |
9797
| Drop referenced column | FAILS (rollback) | Works, FKs BROKEN! |
98+
| Change PK away from referenced col | FAILS (rollback) | Works, FKs BROKEN! |
9899
| Self-referential FK | Works, FKs intact | Works, FKs intact |
99100
| Multiple tables with FKs | Works, FKs intact | Works, FKs intact |
100101

102+
## Cases That Break Incoming FKs
103+
104+
There are **three** scenarios that break incoming FK constraints:
105+
106+
1. **Rename referenced column**: The FK references `authors(id)` but the column is
107+
renamed to `author_pk` - the FK now references a non-existent column.
108+
109+
2. **Drop referenced column**: The FK references `authors(id)` but the column is
110+
dropped entirely - the FK now references a non-existent column.
111+
112+
3. **Remove PK/UNIQUE from referenced column**: SQLite requires that FK targets be
113+
either PRIMARY KEY or UNIQUE. If you change `id` from `INTEGER PRIMARY KEY` to
114+
just `INTEGER`, the FK becomes invalid ("foreign key mismatch") even though the
115+
column still exists.
116+
101117
## Known Issue: Leftover Temp Table on Failure
102118

103119
When `transform()` fails (e.g., due to FK check failure), there may be a leftover
@@ -120,6 +136,62 @@ a data integrity problem.
120136
3. **Use `foreign_key_check`** after bulk operations with FK enforcement off to
121137
verify data integrity
122138

139+
## Automatic Detection and Fixing
140+
141+
### Detection (Easy)
142+
143+
To detect if a transform will break incoming FKs:
144+
145+
```python
146+
def get_incoming_fks(db, table_name):
147+
"""Find all FKs from other tables that reference this table."""
148+
incoming = []
149+
for other_table in db.table_names():
150+
if other_table == table_name:
151+
continue
152+
for fk in db[other_table].foreign_keys:
153+
if fk.other_table == table_name:
154+
incoming.append({
155+
"from_table": fk.table,
156+
"from_column": fk.column,
157+
"to_column": fk.other_column,
158+
})
159+
return incoming
160+
```
161+
162+
Then check if any `to_column` values are in the `rename` or `drop` sets, or if
163+
they're losing their PK/UNIQUE status.
164+
165+
### Automatic Fixing for Column Renames (Moderate Complexity)
166+
167+
For column renames, it's possible to automatically update the referencing tables:
168+
169+
1. Before transforming table A, find all tables with FKs to A
170+
2. For each table B with `FK(col -> A.old_col)`:
171+
- Transform B with `foreign_keys=` parameter to update to `FK(col -> A.new_col)`
172+
3. Then transform A with the rename
173+
174+
### Challenges
175+
176+
- **Circular references**: A -> B -> A requires careful ordering
177+
- **Chain reactions**: Transforming B might affect table C
178+
- **Transaction safety**: All transforms should succeed or all fail
179+
180+
### Proposed API Enhancement
181+
182+
```python
183+
# Option 1: Auto-update with a flag
184+
db["authors"].transform(
185+
rename={"id": "author_pk"},
186+
update_incoming_fks=True # Automatically update FKs in other tables
187+
)
188+
189+
# Option 2: Better error message
190+
# "Cannot rename 'id': books.author_id references this column.
191+
# First transform 'books' to update its foreign key, or use
192+
# update_incoming_fks=True to do this automatically."
193+
```
194+
123195
## Code References
124196

125197
- `transform()` method: sqlite_utils/db.py:1853-1917

0 commit comments

Comments
 (0)