Commit baec67b352 for woocommerce

commit baec67b352d90c6ba4346d09f523212fa3fbb97f
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date:   Wed Feb 4 08:51:09 2026 +0100

    [Performance] Products: edge-case, address attributes metas migration causing perfromance issues (#62966)

    Limit migration to child products only, rather than attempting to migrate all products, for better performance.

diff --git a/plugins/woocommerce/changelog/performance-38877-meta-migration-SQLs-in-read-attributes b/plugins/woocommerce/changelog/performance-38877-meta-migration-SQLs-in-read-attributes
new file mode 100644
index 0000000000..2210c7a2ba
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-38877-meta-migration-SQLs-in-read-attributes
@@ -0,0 +1,4 @@
+Significance: patch
+Type: performance
+
+Performance: optimize attribute meta migration for variable products, addressing edge cases found in older setups.
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php
index ce700d632d..672ec41315 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php
@@ -586,7 +586,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
 	 * @param WC_Product $product Product object.
 	 */
 	protected function read_attributes( &$product ) {
-		$meta_attributes = get_post_meta( $product->get_id(), '_product_attributes', true );
+		$product_id      = $product->get_id();
+		$meta_attributes = get_post_meta( $product_id, '_product_attributes', true );

 		if ( ! empty( $meta_attributes ) && is_array( $meta_attributes ) ) {
 			$attributes = array();
@@ -609,7 +610,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
 						continue;
 					}
 					$id      = wc_attribute_taxonomy_id_by_name( $meta_value['name'] );
-					$options = wc_get_object_terms( $product->get_id(), $meta_value['name'], 'term_id' );
+					$options = wc_get_object_terms( $product_id, $meta_value['name'], 'term_id' );
 				} else {
 					$id      = 0;
 					$options = wc_get_text_attributes( $meta_value['value'] );
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
index 23b5bebcef..c6c1f9af00 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
@@ -34,7 +34,8 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
 	 * @param WC_Product $product Product object.
 	 */
 	protected function read_attributes( &$product ) {
-		$meta_attributes = get_post_meta( $product->get_id(), '_product_attributes', true );
+		$product_id      = $product->get_id();
+		$meta_attributes = get_post_meta( $product_id, '_product_attributes', true );

 		if ( ! empty( $meta_attributes ) && is_array( $meta_attributes ) ) {
 			$attributes   = array();
@@ -52,17 +53,28 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
 					(array) $meta_attribute_value
 				);

-				// Maintain data integrity. 4.9 changed sanitization functions - update the values here so variations function correctly.
+				// Maintain data integrity: WordPress 4.9 changed sanitization functions, and we update the values here so variations function correctly.
+				// As per 2026, we are refactoring the updates into product-level: BC-focused (not all-in on-spot migration), optimized for performance.
+				// Use-case: `_product_attributes` has data populated on WordPress pre-4.8 and containing symbols affected by the breaking changes.
 				if ( $meta_value['is_variation'] && strstr( $meta_value['name'], '/' ) && sanitize_title( $meta_value['name'] ) !== $meta_attribute_key ) {
 					global $wpdb;

-					$old_slug      = 'attribute_' . $meta_attribute_key;
-					$new_slug      = 'attribute_' . sanitize_title( $meta_value['name'] );
-					$old_meta_rows = $wpdb->get_results( $wpdb->prepare( "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s;", $old_slug ) ); // WPCS: db call ok, cache ok.
-
-					if ( $old_meta_rows ) {
-						foreach ( $old_meta_rows as $old_meta_row ) {
-							update_post_meta( $old_meta_row->post_id, $new_slug, $old_meta_row->meta_value );
+					$child_ids = $product->get_children();
+					if ( ! empty( $child_ids ) ) {
+						$products_to_migrate = implode( ', ', $child_ids );
+						$old_slug            = 'attribute_' . $meta_attribute_key;
+						$old_meta_rows       = $wpdb->get_results(
+							$wpdb->prepare(
+								// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+								"SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s AND post_id IN ( $products_to_migrate )",
+								$old_slug
+							)
+						);
+						if ( $old_meta_rows ) {
+							$new_slug = 'attribute_' . sanitize_title( $meta_value['name'] );
+							foreach ( $old_meta_rows as $old_meta_row ) {
+								update_post_meta( $old_meta_row->post_id, $new_slug, $old_meta_row->meta_value );
+							}
 						}
 					}

@@ -75,7 +87,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
 						continue;
 					}
 					$id      = wc_attribute_taxonomy_id_by_name( $meta_value['name'] );
-					$options = wc_get_object_terms( $product->get_id(), $meta_value['name'], 'term_id' );
+					$options = wc_get_object_terms( $product_id, $meta_value['name'], 'term_id' );
 				} else {
 					$id      = 0;
 					$options = wc_get_text_attributes( $meta_value['value'] );
diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php
index 34317d4c79..b397c6af2c 100644
--- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php
+++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php
@@ -706,4 +706,35 @@ class WC_Product_Variable_Data_Store_CPT_Test extends WC_Unit_Test_Case {
 			'Transient should not be set when validation fails'
 		);
 	}
+
+	/**
+	 * Tests `read_attributes` for handling metas migration due to sanitize_title BC breaks.
+	 */
+	public function test_read_attributes_addresses_bc_break_in_sanitize(): void {
+		$product    = WC_Helper_Product::create_variation_product();
+		$product_id = $product->get_id();
+		$child_ids  = array_values( $product->get_children() );
+
+		// Patch up the metas to match pre-BC state.
+		$attributes                      = get_post_meta( $product_id, '_product_attributes', true );
+		$attributes['Size/Size']         = $attributes['pa_size'];
+		$attributes['Size/Size']['name'] = 'Size/Size';
+		unset( $attributes['pa_size'] );
+		update_post_meta( $product_id, '_product_attributes', $attributes );
+		foreach ( $child_ids as $child_id ) {
+			update_post_meta( $child_id, 'attribute_Size/Size', get_post_meta( $child_id, 'attribute_pa_size', true ) );
+			delete_post_meta( $child_id, 'attribute_pa_size' );
+		}
+
+		// Reload the product object, so the migration is executed.
+		$product = wc_get_product( $product_id );
+
+		// Verify the migrated entries and cleanup.
+		$sizes = array( 'small', 'large', 'huge', 'huge', 'huge', 'huge' );
+		foreach ( $child_ids as $index => $child_id ) {
+			$this->assertSame( $sizes[ $index ], get_post_meta( $child_id, 'attribute_size-size', true ) );
+			$this->assertSame( $sizes[ $index ], get_post_meta( $child_id, 'attribute_Size/Size', true ) );
+		}
+		$product->delete();
+	}
 }