Skip to content

Fix batch pagination bug in migrations and add undo support#587

Merged
akirk merged 4 commits intomainfrom
migration-hardening
Mar 4, 2026
Merged

Fix batch pagination bug in migrations and add undo support#587
akirk merged 4 commits intomainfrom
migration-hardening

Conversation

@akirk
Copy link
Copy Markdown
Owner

@akirk akirk commented Feb 6, 2026

Summary

  • Fix critical batch pagination bug in 3 migrations (migrate_post_tags_batch, migrate_activitypub_attributed_to_batch, convert_replies_to_comments_batch) that silently skipped ~50% of items when processing more than one batch. All three used LIMIT X OFFSET Y while removing processed items from the result set, causing the offset to advance past unprocessed rows.
  • Add backfill_mention_tags to the 4.0.0 auto-upgrade path — it's a prerequisite for convert_replies_to_comments and is non-destructive/local-only.
  • Add recovery data storage during tag migration and undo support for tag and reply-to-comment migrations, with an "Undo" button in the migrations admin UI.

Details

Offset fix approach per migration

Migration Why items were skipped Fix
migrate_post_tags_batch DELETE removes relationships → next batch offset skips unprocessed rows Always OFFSET 0; deleted rows naturally expose next batch
migrate_activitypub_attributed_to_batch Successful posts gain ap_actor_id and drop from NOT LIKE filter, but failed posts stay and offset advances past them Always OFFSET 0; store ap_actor_id=0 sentinel on failure
convert_replies_to_comments_batch Converted posts are trashed, but skipped posts remain; offset counts both Always OFFSET 0; mark all processed posts with _friends_reply_checked meta + NOT EXISTS subquery

New tests

  • test_migrate_post_tags_batch_boundary — 150 posts (exceeds batch_size=100), verifies all migrated
  • test_undo_post_tags_migration — full round-trip: migrate → verify recovery → undo → verify restored
  • test_full_upgrade_path_from_3_6_0 — exercises upgrade_plugin() end-to-end

Test plan

  • composer test — 247 tests, 1051 assertions pass
  • composer check-cs — clean
  • Manual test: run tag migration on migrations admin page, verify undo button appears, click undo, verify post_tags restored

WordPress Playground

https://playground.wordpress.net/#{%22steps%22:[{%22step%22:%22installPlugin%22,%22pluginData%22:{%22resource%22:%22git:directory%22,%22url%22:%22https://github.com/akirk/friends%22,%22ref%22:%22migration-hardening%22,%22refType%22:%22branch%22},%22options%22:{%22activate%22:true}}]}

Three batch migrations used LIMIT/OFFSET pagination while also removing
processed items from the result set, causing ~50% of items to be silently
skipped. Fix all three to always query from OFFSET 0:

- migrate_post_tags_batch: deleted relationships naturally expose next batch
- migrate_activitypub_attributed_to_batch: add ap_actor_id=0 sentinel on
  fetch failure so posts drop out of the NOT LIKE filter
- convert_replies_to_comments_batch: mark processed posts with
  _friends_reply_checked meta and add NOT EXISTS subquery

Also add backfill_mention_tags to the 4.0.0 auto-upgrade path (prerequisite
for convert_replies_to_comments), store recovery data during tag migration,
and add undo support for tag and reply-to-comment migrations with admin UI.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 6, 2026

Test this PR in WordPress Playground

You can test this pull request directly in WordPress Playground:

Launch WordPress Playground

This will install and activate the plugin with the changes from this PR.

@akirk akirk force-pushed the migration-hardening branch from f1f0bed to 65b7d7f Compare February 7, 2026 06:21
Make the Migrations page accessible via the settings tab bar instead of
being a hidden page. Only shown to users with manage_options capability.
Uses the shared settings-header template for consistent navigation.
@akirk akirk force-pushed the migration-hardening branch from 65b7d7f to 1c4ba62 Compare February 7, 2026 06:36
The migration had no status_option, so completion was inferred from
the stored plugin version — which may still be < 4.0.0 during an upgrade.
Add a status_option and set it in all return paths of the method.
@akirk akirk merged commit 445aea4 into main Mar 4, 2026
25 checks passed
@akirk akirk deleted the migration-hardening branch March 4, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant