fix(migrations): actually drop _customer_location_uc (list == set no-op) - #42642
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #42642 +/- ##
==========================================
- Coverage 65.79% 65.79% -0.01%
==========================================
Files 2842 2842
Lines 162127 162128 +1
Branches 37158 37158
==========================================
- Hits 106676 106673 -3
- Misses 53387 53390 +3
- Partials 2064 2065 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Migration df3d7e2eb9a4 intended to drop the legacy 3-column unique constraint _customer_location_uc (database_id, schema, table_name) from the tables table, but passed a list to generic_find_uq_constraint_name, whose body compares columns == set(uq['column_names']). list == set is always False in Python, so the constraint was never found and never dropped — a silent no-op. Databases migrated through it therefore still carry the constraint, which leaks across catalogs (no catalog leg) and diverges from schemas built from model metadata, where the model's 4-column constraint exists instead. A row can occupy (database_id, schema, table_name) across catalogs, passing every catalog-aware app-level check and then hitting an opaque IntegrityError only on migrated databases. Fix in two parts: - generic_find_uq_constraint_name coerces its columns argument to a set, removing the foot-gun for all callers (existing set-passing callers unaffected). - A take-2 migration re-attempts the drop with exact set matching. No-op where the constraint is already absent; the model's 4-column constraint can never match a 3-column set comparison. Downgrade restores the legacy constraint best-effort, mirroring the tolerant posture of its original creator. Verified on SQLite and PostgreSQL: fresh full-chain upgrade, then downgrade (constraint recreated) and re-upgrade (constraint dropped), with uq_tables_uuid untouched throughout. Fixes sc-112173. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
01ee065 to
b706770
Compare
|
The flagged issue is correct. The migration header's Revision ID: 16755d4ca4ae
Revises: f3a8c1d2e9b7
Create Date: 2026-07-31 10:00:00.000000There are no other comments on this pull request to address. superset/migrations/versions/2026-07-31_10-00_16755d4ca4ae_drop__customer_location_uc_take_2.py |
The rebase onto current master re-pointed down_revision from e7d93a524ff6 to f3a8c1d2e9b7 — master gained the reports-retry migration (apache#42481) claiming the same parent, which left two alembic heads — but the docstring header still named the old ancestor. Alembic reads the variable, not the docstring, so the graph was correct and CI passed. The header was simply lying to the next person to read it, which is exactly when a migration file gets read: while diagnosing a broken chain. Caught by codeant and bito independently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code Review Agent Run #1185aeActionable Suggestions - 0Filtered by Review RulesBito filtered these suggestions based on rules created automatically for your feedback. Manage rules.
Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
rusackas
left a comment
There was a problem hiding this comment.
@mikebridge LGTM, thanks for pinning the exact-match guarantee with its own test.
Flips the two soft-delete release defaults for general availability (sc-111918 stage 2a, FR-003 and FR-005): SOFT_DELETE False -> True SOFT_DELETE_PURGE_DRY_RUN True -> False Deleting a dashboard, chart, or dataset now archives it rather than removing it, and the nightly purge deletes for real once the retention window elapses instead of only logging would_purge counts. Both switches are RETAINED, deliberately. FR-003 keeps SOFT_DELETE as the move-back lever and defers its removal (with the hard-delete fallback and its both-state tests) to sc-115600 once post-flip confidence is established; FR-004 requires that fallback to keep being exercised, because the way back is only real if it stays tested. FR-005 keeps SOFT_DELETE_PURGE_DRY_RUN as an operational switch, so an operator can suspend purging without redeploying. Scope: soft delete only. The versioning flips (VERSION_HISTORY and ENABLE_VERSIONING_CAPTURE, FR-001/002/008) are a separate branch — FR-009 permits the two shipping in different releases, and they answer to different gates: an internal dev@ determination here, a SIP-210 vote there. test_default_config_is_safe asserted the pre-flip posture and is superseded by test_default_config_purges_for_real_after_the_retention_window, which pins the new defaults with the reasoning for why dry-run was the introducing release's choice. It is the only test in the unit suite that the flip breaks — verified by a full run (the 24 firebolt SQL-dialect failures are a different subsystem and unrelated). UPDATING.md gains the FR-006 breaking-change entry: what changes, how to size the first live purge (dry-run for one night and read the counts), the replaced-CELERY_CONFIG check, and the honest caveat that turning soft delete back off resurrects archived rows rather than cleanly reverting. The dry-run paragraph further down, which described dry-run as the shipped default, is corrected. docs/static/feature-flags.json is regenerated by the docs-sync hook. Depends on: apache#42641 (beat-schedule startup warning, FR-010) and apache#42642 (sc-112173 no-op migration, FR-010) landing first. Verified: 66/66 across the soft-delete unit suites, 24/24 integration purge tests, pre-commit green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SUMMARY
Migration
df3d7e2eb9a4(2024) intended to drop the legacy 3-column unique constraint_customer_location_uc(database_id, schema, table_name)fromtables, but passed a list togeneric_find_uq_constraint_name, whose body comparescolumns == set(uq["column_names"]).list == setis alwaysFalsein Python, so the constraint was never found and never dropped — a silent no-op.Consequences on databases migrated through it:
catalogleg) is still present, while schemas built from model metadata (create_all, e.g. CI) carry the model's intended 4-column constraint(database_id, catalog, schema, table_name)instead — so CI cannot reproduce production failure shapes.(database_id, schema, table_name)across catalogs: creating the same table under a different catalog passes every catalog-aware app-level check and then hits an opaqueIntegrityErroronly on migrated databases.Two-part fix:
generic_find_uq_constraint_namenow coerces itscolumnsargument to a set (parameter widened toCollection[str]), removing the foot-gun for all callers. Existing set-passing callers are unaffected.16755d4ca4ae) re-attempts the drop with exact set matching. It is a harmless no-op where the constraint is already absent, and the model's 4-column constraint can never match a 3-column set comparison, so it is not at risk. Downgrade restores the legacy constraint best-effort (rows written after the drop may legitimately collide across catalogs), mirroring the tolerant try/except posture of the constraint's original creator.BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A — schema-only change.
TESTING INSTRUCTIONS
pytest tests/unit_tests/utils/test_core.py -k generic_find_uq— 3 tests: the list-argument regression pin (fails against the pre-fix helper), the set-argument happy path, and the exact-match guarantee (a 3-column lookup never matches the model's 4-column constraint).superset db upgrade(clean), thensuperset db downgrade(constraint recreated:_customer_location_uc (database_id, schema, table_name)), thensuperset db upgrade(constraint dropped), withuq_tables_uuiduntouched throughout. On a database that still carries the legacy constraint, the upgrade drops it; on one that never had it, the lookup finds nothing and the migration no-ops.ADDITIONAL INFORMATION
ALTER TABLE ... DROP CONSTRAINTontables(or a no-op lookup); sub-second on PostgreSQL/MySQL, table-rebuild via batch mode on SQLite. No data movement, no downtime expected.🤖 Generated with Claude Code