geenforce: fix re-publish FK crash on MySQL (stale entries in draft rebuild)
replace_scope_draft deleted old draft entries with per-object db.session.delete but left the deleted objects in scope.entries. On a re-publish a caller (seed_display_scope) then matched a stale deleted entry via next() and store_inline_payload attached a payload to its dead entryid, failing the manifestpayloads->manifestentries FK on MySQL (1452); SQLite does not enforce it so the idempotency test passed. Clear the collection via the delete-orphan cascade instead, and flush pending inserts before the bulk payload delete so its autoflush cannot interleave a half-built insert. Verified publish + re-publish x3 on MySQL 5.6.
This commit is contained in:
@@ -57,8 +57,14 @@ def replace_scope_draft(scopename, phase, manifest):
|
|||||||
scope.site = manifest.get('Site')
|
scope.site = manifest.get('Site')
|
||||||
scope.iscommon = (scopename == 'common')
|
scope.iscommon = (scopename == 'common')
|
||||||
|
|
||||||
for entry in list(scope.entries):
|
# Clear existing draft entries via the delete-orphan cascade. This also
|
||||||
db.session.delete(entry)
|
# EMPTIES the in-memory collection, so a later lookup on scope.entries cannot
|
||||||
|
# match a stale (deleted) entry. A per-object db.session.delete() leaves the
|
||||||
|
# deleted objects in scope.entries until expiry, which on a re-publish made a
|
||||||
|
# caller (seed_display_scope) pick an old deleted entry and attach a payload
|
||||||
|
# to its dead entryid -> MySQL FK 1452 (SQLite does not enforce it, so the
|
||||||
|
# idempotency test missed it).
|
||||||
|
scope.entries.clear()
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
for i, entry_dict in enumerate(manifest.get('Applications') or []):
|
for i, entry_dict in enumerate(manifest.get('Applications') or []):
|
||||||
@@ -352,7 +358,15 @@ def store_inline_payload(entry, filename, contenttype, rawbytes):
|
|||||||
Returns the ManifestPayload.
|
Returns the ManifestPayload.
|
||||||
"""
|
"""
|
||||||
sha = hashlib.sha256(rawbytes).hexdigest()
|
sha = hashlib.sha256(rawbytes).hexdigest()
|
||||||
ManifestPayload.query.filter_by(entryid=entry.entryid).delete()
|
# Flush any pending inserts (e.g. a prior entry's inline payload) BEFORE the
|
||||||
|
# bulk delete. Otherwise the delete's autoflush interleaves that half-built
|
||||||
|
# INSERT and, on MySQL, fails the manifestpayloads->manifestentries FK
|
||||||
|
# (SQLite does not enforce it the same way, so tests miss this). Then delete
|
||||||
|
# existing rows for this entry with autoflush off + no session-sync.
|
||||||
|
db.session.flush()
|
||||||
|
with db.session.no_autoflush:
|
||||||
|
ManifestPayload.query.filter_by(
|
||||||
|
entryid=entry.entryid).delete(synchronize_session=False)
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
payload = ManifestPayload(
|
payload = ManifestPayload(
|
||||||
entryid=entry.entryid,
|
entryid=entry.entryid,
|
||||||
|
|||||||
Reference in New Issue
Block a user