Commit 6be0ead2c0d for woocommerce
commit 6be0ead2c0d028a2af18983270b55c98ed30c456
Author: Mike Jolley <mike.jolley@me.com>
Date: Thu Nov 5 15:31:47 2026 +0000
Keep the whole session when another request deletes it before saving (#70200)
* Save the whole session when the stored row was deleted during a merge
A row deleted by another request between load and save left merge()
with nothing to apply changes to, so it saved only the changed keys and
dropped the rest of the session. Treat a missing row like a failed read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add changelog entry for deleted session row merge fix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use targeted assertions in the deleted session row test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/fix-session-merge-deleted-row b/plugins/woocommerce/changelog/fix-session-merge-deleted-row
new file mode 100644
index 00000000000..aaefd515a54
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-session-merge-deleted-row
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+Comment: Fix unreleased session merge dropping unchanged keys when the row was deleted mid-request (#69559).
+
diff --git a/plugins/woocommerce/src/Internal/Session/SessionDataWriter.php b/plugins/woocommerce/src/Internal/Session/SessionDataWriter.php
index 09fcd6c58c5..2a7b6c8bd65 100644
--- a/plugins/woocommerce/src/Internal/Session/SessionDataWriter.php
+++ b/plugins/woocommerce/src/Internal/Session/SessionDataWriter.php
@@ -48,7 +48,7 @@ class SessionDataWriter {
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted table name.
$value = $wpdb->get_var( $wpdb->prepare( "SELECT session_value FROM {$table} WHERE session_key = %s", $session_key ) );
- if ( '' !== $wpdb->last_error ) {
+ if ( null === $value || '' !== $wpdb->last_error ) {
// Without the stored row there is nothing to apply the changes to, so save this request's whole session.
return $this->replace( $table, $session_key, $expiry, $current );
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Session/SessionDataWriterTest.php b/plugins/woocommerce/tests/php/src/Internal/Session/SessionDataWriterTest.php
index 144fca284e6..fe231d23f38 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Session/SessionDataWriterTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Session/SessionDataWriterTest.php
@@ -179,6 +179,37 @@ class SessionDataWriterTest extends WC_Unit_Test_Case {
$this->assertSame( $expected, $this->get_stored(), 'Keys this request did not change should not be dropped' );
}
+ /**
+ * @testdox When another request deleted the stored row since this one loaded, saves this request's whole session instead of only its changes.
+ */
+ public function test_saves_whole_session_when_stored_row_was_deleted(): void {
+ $saved = $this->sut->merge(
+ $this->table,
+ self::SESSION_KEY,
+ time() + 100,
+ array(
+ 'a' => 'mine',
+ 'keep' => 'kept',
+ ),
+ array(
+ 'a' => 'old',
+ 'keep' => 'kept',
+ )
+ );
+
+ $stored = $this->get_stored();
+
+ $this->assertArrayHasKey( 'a', $saved );
+ $this->assertSame( 'mine', $saved['a'] );
+ $this->assertArrayHasKey( 'keep', $saved );
+ $this->assertSame( 'kept', $saved['keep'] );
+
+ $this->assertArrayHasKey( 'a', $stored );
+ $this->assertSame( 'mine', $stored['a'] );
+ $this->assertArrayHasKey( 'keep', $stored, 'Keys this request did not change should not be dropped' );
+ $this->assertSame( 'kept', $stored['keep'] );
+ }
+
/**
* Store a session row directly.
*