Fix silent insert failure in tables-based progress repositories#7917
Fix silent insert failure in tables-based progress repositories#7917
Conversation
Add error handling for failed database inserts in the lesson and quiz tables-based progress repositories. When $wpdb->insert() fails and returns false, throw a RuntimeException instead of silently continuing with insert_id of 0, which would create a fake success object and lead to data corruption. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a silent data-loss bug in the tables-based student progress repositories. Previously, Tables_Based_Lesson_Progress_Repository::create() and Tables_Based_Quiz_Progress_Repository::create() ignored the return value of $wpdb->insert(). A failed insert would silently continue with insert_id = 0, producing a fake progress object with a zero ID that could corrupt downstream operations. The fix stores the return value and throws a \RuntimeException immediately on failure.
Changes:
- Add
$resultvariable to capture$wpdb->insert()return value and throw a\RuntimeExceptionif it equalsfalsein bothTables_Based_Lesson_Progress_RepositoryandTables_Based_Quiz_Progress_Repository. - Add
@throws \RuntimeExceptionPHPDoc annotation to thecreate()methods of both repositories. - Add a changelog entry for the fix.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
includes/internal/student-progress/lesson-progress/repositories/class-tables-based-lesson-progress-repository.php |
Captures insert() return value and throws RuntimeException on failure; adds @throws docblock |
includes/internal/student-progress/quiz-progress/repositories/class-tables-based-quiz-progress-repository.php |
Same fix applied to the quiz progress repository |
changelog/fix-tables-based-repo-insert-error-handling |
New changelog entry describing the patch |
Note: The identical bug exists in
Tables_Based_Course_Progress_Repository::create()(line 83 ofclass-tables-based-course-progress-repository.php) but was not addressed in this PR.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
…ries Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4de92e2 to
2a82d54
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- start_user_on_course: return false (not 0) to match docblock contract - Migration task: always soft-delete comment even on failure to prevent infinite retry of permanently failing records - Document partial data risk in loop-based catch blocks (retry is the correct recovery path since it delete_all + re-inserts) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exception messages are for logging, not display. esc_html() corrupts diagnostic data (e.g. converting & to & in log files). Escape at the point of output instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
d230108 to
e74c457
Compare
…loss, improve docblocks Add RuntimeException when $wpdb->insert() fails in tables-based repository create() methods, replacing silent failures with detected-and-logged ones. Wrap secondary store operations in aggregate repositories with try-catch so the new exceptions from secondary sync writes are caught and logged rather than propagating up to callers. Fix critical data loss bug in legacy quiz migration where wp_delete_comment ran even when the insert failed. Replace uncaught throw in quiz progress save() with error_log + return since primary save already completed. Add proper @throws annotations with correct ordering and context-appropriate wording. Add phpcs:ignore comments for error_log calls. Fix anonymous function spacing in grade repositories. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
e74c457 to
c1c8292
Compare
The batch query relies on soft-delete to progress — it fetches comments with status 'log' without offset. Keeping continue on failure would leave the comment undeleted, causing it to be retried forever. The soft-delete moves the comment to trash (recoverable), and the error log captures the comment ID, quiz ID, and user ID for manual recovery if needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove continue in migration task to prevent infinite retry loop — the batch query relies on soft-delete to progress. Clarify mid-loop failure comments in quiz answer/grade creation loops. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These calls would never display notices because the code runs in admin_init handlers that redirect after processing, not on Settings API pages where settings_errors() is rendered. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 37 out of 37 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
includes/update-tasks/class-sensei-update-legacy-quiz-data.php:74
run_batch()soft-deletes the legacy answer comment unconditionally. If the insert into the new tables fails (caughtRuntimeException), the legacy comment is still deleted, which can permanently lose the only copy of the student’s answer/grade and prevents a retry on the next batch run. Only delete the comment after a successful migration, orcontinuefrom the catch block without deleting so the batch can retry later.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
Use explicit ?string syntax instead of implicit string = null to avoid PHP 8.1+ deprecation warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@merkushin This one introduces logic for handling insert failures to the custom table. Just inserts for now but will expand to include other database operations once the approach is vetted. |
mdawaffe
left a comment
There was a problem hiding this comment.
Most things just error_log(). Some things throw. A few things call Sensei()->notices->add_notice(). Is there a pattern for when things should do what?
| * @param int $question_id The question ID. | ||
| * @param string $value The answer value. | ||
| * | ||
| * @throws \RuntimeException If answer creation fails. |
There was a problem hiding this comment.
This (and most of the others) never actually throw. Should they?
Summary
$wpdb->insert()returnsfalseon failure, but none of the tables-based repositories checked this return value. When an insert failed, the code silently continued with$wpdb->insert_idof0, constructing invalid objects and losing student data without any indication.This PR:
RuntimeExceptionwhen$wpdb->insert()returnsfalsein all tables-based repositories (progress, submission, answer, grade)@throwsannotations to repository interfaces and implementationsTest plan
Happy path
Error handling
To simulate a database insert failure, temporarily rename the progress table:
With
WP_DEBUG_LOGenabled and storage set to tables:wp-content/debug.log🤖 Generated with Claude Code