From 9ff18b3e4877b42547e2d2c3684f2b5df4f79a8d Mon Sep 17 00:00:00 2001 From: Mike Bridge Date: Thu, 21 May 2026 09:33:12 -0600 Subject: [PATCH] fix(versioning): flag ``description`` instead of ``uuid`` in force-parent-dirty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flag_modified(parent, "uuid") was producing FK integrity failures via the column's BLOB/BINARY round-trip: SQLAlchemy logs the param as ```` and the UUID round-trip doesn't always match the in-memory value byte-for-byte. Symptom: in scenarios where the parent is already going to flush (Reverter applying historical state during restore, RLS test triggering autoflush during a query), our added ``uuid`` UPDATE column tripped the FK check. Pick ``description`` instead — plain Text column on all three versioned parent classes (Dashboard, Slice, SqlaTable), no TypeDecorator, no marshaling layer. Flagging it round-trips its current value safely. Fallback chain ``description → uuid → col_keys[0]`` keeps the original deterministic-pick property for forks/subclasses that excluded ``description``. Should unblock test_restore_applies_scalar_field and the test_rls_filter_alters_no_role_user_birth_names_query autoflush error. --- superset/versioning/baseline.py | 36 ++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/superset/versioning/baseline.py b/superset/versioning/baseline.py index d96e9ad2804..f584d372b21 100644 --- a/superset/versioning/baseline.py +++ b/superset/versioning/baseline.py @@ -134,22 +134,34 @@ def _force_parent_dirty_on_child_change(session: Session) -> None: col_keys = [prop.key for prop in versioned_column_properties(parent)] if not col_keys: continue - # ``uuid`` is on all three versioned parent classes (Dashboard, - # Slice, SqlaTable) and is in none of their ``__versioned__`` - # excludes — pick it deterministically so the flagged attribute - # is stable across SQLAlchemy versions / mapper-configuration - # orders. Falls back to the first available column for forks or - # subclasses that excluded ``uuid``. - flag_col = "uuid" if "uuid" in col_keys else col_keys[0] + # ``description`` is a plain ``Text`` column on all three versioned + # parent classes (Dashboard, Slice, SqlaTable) and is in none of + # their ``__versioned__`` excludes — pick it deterministically so + # the flagged attribute is stable across SQLAlchemy versions / + # mapper-configuration orders. We deliberately avoid ``uuid`` + # here: when a versioned-parent UPDATE goes through with ``uuid`` + # flagged, the column's ``UUIDType``/BLOB round-trip produces a + # memoryview that fails an FK integrity check on some dialects + # (observed in ``test_rls_filter_alters_no_role_user_birth_names_query`` + # and ``test_restore_applies_scalar_field``). ``description`` is + # a plain text column with no marshaling layer, so flagging it + # safely round-trips its current value. Falls back to ``uuid`` + # then ``col_keys[0]`` for forks that excluded ``description``. + if "description" in col_keys: + flag_col = "description" + elif "uuid" in col_keys: + flag_col = "uuid" + else: + flag_col = col_keys[0] try: attributes.flag_modified(parent, flag_col) except InvalidRequestError: # The parent is a freshly-constructed ``session.new`` instance - # whose ``uuid`` default (``default=uuid4``) hasn't fired yet - # — the attribute is unloaded in instance state, so - # ``flag_modified`` rejects it. The parent will INSERT in this - # flush regardless, so the flag was redundant; safely skip. - # Hit by ``test_create_dataset_item`` (POST /api/v1/dataset/). + # whose attribute defaults haven't fired yet — the attribute + # is unloaded in instance state, so ``flag_modified`` rejects + # it. The parent will INSERT in this flush regardless, so the + # flag was redundant; safely skip. Hit by + # ``test_create_dataset_item`` (POST /api/v1/dataset/). continue