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();
+ }
}