refactor(2meet-data-optimizer): stop registering the wc_product post group
- WooCommerce reads product meta directly and writes stock by SQL, so a flat copy drifted (R-2.1 V1 T4); product meta now stays in native only (plan decision 2). TMDO_Post_Fields registers wp_core, attachment and nav_menu_item. Existing wp_wpdo_post_wc_product tables are left in place and still dropped on uninstall; the orphan restore registers nothing for them while native meta holds the keys; `wp tmdo bridge-restore-native post --group=wc_product --retire` archives them. - Tests that route products register the former group themselves (tmdo_test_register_wc_product_group(), same name and table); live router Q06 runs on wp_core's _wp_page_template instead of _sku. The post stress tester already skips flat tables that do not exist. - Verify: PostFieldsRegistrationTest (RED before); dev36: wc_product absent from the registry and not restored as an orphan, a CLI order took stock 452 → 450 in native with no DB error (reverted), drift post 10 groups 0. - PR: PR-077
This commit is contained in:
@@ -211,7 +211,7 @@ class TMDO_CLI_Post {
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp wpdo post-migrate-group --group=wc_product
|
||||
* wp wpdo post-migrate-group --group=hp_listing_core
|
||||
* wp wpdo post-migrate-group --group=hp_listing_core
|
||||
*
|
||||
* @param array $args Positional arguments (unused).
|
||||
|
||||
@@ -55,7 +55,7 @@ final class TMDO_Post_Shadow_Verifier {
|
||||
* is available.
|
||||
*
|
||||
* @param string $post_type WP post_type to sample.
|
||||
* @param string $group_name Entity group name (e.g. 'wc_product').
|
||||
* @param string $group_name Entity group name (e.g. 'hp_listing_core').
|
||||
* @param string $flat_table Fully qualified flat table name.
|
||||
* @param string[] $keys Meta keys to compare (each post checks all).
|
||||
* @param int $sample_size Number of posts to sample (capped to DB count).
|
||||
|
||||
@@ -2,19 +2,18 @@
|
||||
/**
|
||||
* Post entity field registration for WP Data Optimizer (v2.9.1).
|
||||
*
|
||||
* Registers four post entity groups designed to absorb the bulk of
|
||||
* Registers three post entity groups designed to absorb the bulk of
|
||||
* wp_postmeta rows that previously bypassed any flat-table strategy.
|
||||
* All groups use TMDO_Entity_Registry → flat tables instead of wp_postmeta EAV.
|
||||
*
|
||||
* Groups → flat tables (post_type targets):
|
||||
* wp_core → wp_wpdo_post_wp_core (cross post_type)
|
||||
* attachment → wp_wpdo_post_attachment (attachment)
|
||||
* wc_product → wp_wpdo_post_wc_product (product)
|
||||
* nav_menu_item → wp_wpdo_post_nav_menu_item (nav_menu_item)
|
||||
*
|
||||
* Pattern mirrors TMDO_Member_Fields::register(). Called from
|
||||
* TMDO_Core::run() before the wpdo_register_entity_fields action fires;
|
||||
* Schema_Manager materializes the four flat tables in init:1
|
||||
* Schema_Manager materializes the three flat tables in init:1
|
||||
* via process_pending_migrations() (idempotent via schema_hash compare).
|
||||
*
|
||||
* Keys not registered here (HivePress dynamic attrs, WPCS legacy keys,
|
||||
@@ -39,7 +38,10 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers four post entity groups.
|
||||
* Registers three post entity groups. WooCommerce product meta is not one of
|
||||
* them (1.2.0): WooCommerce reads it directly and writes stock by SQL, so
|
||||
* native meta stays its only copy. Existing wp_wpdo_post_wc_product tables
|
||||
* are left alone (see `wp tmdo bridge-restore-native post --group=wc_product --retire`).
|
||||
*/
|
||||
final class TMDO_Post_Fields {
|
||||
|
||||
@@ -49,7 +51,6 @@ final class TMDO_Post_Fields {
|
||||
*/
|
||||
private const GROUP_POST_TYPES = array(
|
||||
'attachment' => 'attachment',
|
||||
'wc_product' => 'product',
|
||||
'nav_menu_item' => 'nav_menu_item',
|
||||
);
|
||||
|
||||
@@ -104,7 +105,6 @@ final class TMDO_Post_Fields {
|
||||
|
||||
self::register_wp_core_group();
|
||||
self::register_attachment_group();
|
||||
self::register_wc_product_group();
|
||||
self::register_nav_menu_item_group();
|
||||
}
|
||||
|
||||
@@ -176,131 +176,6 @@ final class TMDO_Post_Fields {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce product core meta keys (post_type=product).
|
||||
* 19 keys — covers ~94% of wp_postmeta rows for products on dev10.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function register_wc_product_group(): void {
|
||||
TMDO_Entity_Registry::register_group(
|
||||
'post',
|
||||
'wc_product',
|
||||
array(
|
||||
array(
|
||||
'key' => '_price',
|
||||
'type' => 'decimal',
|
||||
'searchable' => true,
|
||||
'label' => 'Effective price',
|
||||
),
|
||||
array(
|
||||
'key' => '_regular_price',
|
||||
'type' => 'decimal',
|
||||
'searchable' => true,
|
||||
'label' => 'Regular price',
|
||||
),
|
||||
array(
|
||||
'key' => '_sale_price',
|
||||
'type' => 'decimal',
|
||||
'searchable' => true,
|
||||
'label' => 'Sale price',
|
||||
),
|
||||
array(
|
||||
'key' => '_stock',
|
||||
'type' => 'integer',
|
||||
'searchable' => true,
|
||||
'label' => 'Stock quantity',
|
||||
),
|
||||
array(
|
||||
'key' => '_stock_status',
|
||||
'type' => 'enum',
|
||||
'searchable' => true,
|
||||
'options' => array( 'instock', 'outofstock', 'onbackorder' ),
|
||||
'label' => 'Stock status',
|
||||
),
|
||||
array(
|
||||
'key' => '_sku',
|
||||
'type' => 'text',
|
||||
'searchable' => true,
|
||||
'label' => 'Product SKU',
|
||||
),
|
||||
array(
|
||||
'key' => '_manage_stock',
|
||||
'type' => 'enum',
|
||||
'options' => array( 'yes', 'no' ),
|
||||
'label' => 'Manage stock?',
|
||||
),
|
||||
array(
|
||||
'key' => '_backorders',
|
||||
'type' => 'enum',
|
||||
'options' => array( 'yes', 'no', 'notify' ),
|
||||
'label' => 'Allow backorders?',
|
||||
),
|
||||
array(
|
||||
'key' => '_sold_individually',
|
||||
'type' => 'enum',
|
||||
'options' => array( 'yes', 'no' ),
|
||||
'label' => 'Sold individually?',
|
||||
),
|
||||
array(
|
||||
'key' => '_virtual',
|
||||
'type' => 'enum',
|
||||
'options' => array( 'yes', 'no' ),
|
||||
'label' => 'Virtual product?',
|
||||
),
|
||||
array(
|
||||
'key' => '_downloadable',
|
||||
'type' => 'enum',
|
||||
'options' => array( 'yes', 'no' ),
|
||||
'label' => 'Downloadable?',
|
||||
),
|
||||
array(
|
||||
'key' => '_tax_class',
|
||||
'type' => 'text',
|
||||
'label' => 'Tax class slug',
|
||||
),
|
||||
array(
|
||||
'key' => '_tax_status',
|
||||
'type' => 'enum',
|
||||
'options' => array( 'taxable', 'shipping', 'none' ),
|
||||
'label' => 'Tax status',
|
||||
),
|
||||
array(
|
||||
'key' => '_download_limit',
|
||||
'type' => 'integer',
|
||||
'label' => 'Download limit',
|
||||
),
|
||||
array(
|
||||
'key' => '_download_expiry',
|
||||
'type' => 'integer',
|
||||
'label' => 'Download expiry days',
|
||||
),
|
||||
array(
|
||||
'key' => '_product_version',
|
||||
'type' => 'text',
|
||||
'label' => 'WC version product was created on',
|
||||
),
|
||||
array(
|
||||
'key' => '_wc_average_rating',
|
||||
'type' => 'decimal',
|
||||
'searchable' => true,
|
||||
'label' => 'Average rating',
|
||||
),
|
||||
array(
|
||||
'key' => '_wc_review_count',
|
||||
'type' => 'integer',
|
||||
'label' => 'Review count',
|
||||
),
|
||||
array(
|
||||
'key' => 'total_sales',
|
||||
'type' => 'integer',
|
||||
'searchable' => true,
|
||||
'label' => 'Total sales count',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nav menu item meta keys (post_type=nav_menu_item).
|
||||
*
|
||||
|
||||
@@ -102,7 +102,7 @@ final class TMDO_Post_Migration {
|
||||
* into the group's flat table. Groups with json fields go row by row
|
||||
* through backfill_group_json() instead.
|
||||
*
|
||||
* @param string $group_name Entity group name (e.g. 'wc_product').
|
||||
* @param string $group_name Entity group name (e.g. 'hp_listing_core').
|
||||
* @return array{migrated:int,group:string,post_type:string|null}
|
||||
* @throws InvalidArgumentException When group is not registered, or is a restored orphan group.
|
||||
* @throws RuntimeException When the pivot SQL fails.
|
||||
|
||||
@@ -129,6 +129,7 @@ class PostEntityLifecycleTest extends TestCase {
|
||||
WPDO_Entity_Registry::init();
|
||||
WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() );
|
||||
WPDO_Post_Fields::register_entity_fields();
|
||||
tmdo_test_register_wc_product_group();
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void {
|
||||
|
||||
@@ -93,6 +93,7 @@ class PostMigrationTest extends TestCase {
|
||||
WPDO_Entity_Registry::init();
|
||||
WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() );
|
||||
WPDO_Post_Fields::register_entity_fields();
|
||||
tmdo_test_register_wc_product_group();
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void {
|
||||
|
||||
@@ -15,6 +15,7 @@ class PostQueryRouterDriftTest extends TestCase {
|
||||
WPDO_Entity_Registry::init();
|
||||
WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() );
|
||||
WPDO_Post_Fields::register_entity_fields();
|
||||
tmdo_test_register_wc_product_group();
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void {
|
||||
|
||||
@@ -20,6 +20,7 @@ class PostQueryRouterOrderTest extends TestCase {
|
||||
WPDO_Entity_Registry::init();
|
||||
WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() );
|
||||
WPDO_Post_Fields::register_entity_fields();
|
||||
tmdo_test_register_wc_product_group();
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
|
||||
@@ -39,6 +39,7 @@ class PostQueryRouterTest extends TestCase {
|
||||
WPDO_Entity_Registry::init();
|
||||
WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() );
|
||||
WPDO_Post_Fields::register_entity_fields();
|
||||
tmdo_test_register_wc_product_group();
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void {
|
||||
|
||||
@@ -649,3 +649,43 @@ if ( ! class_exists( 'TMDO_Listing_Stats' ) ) {
|
||||
}
|
||||
class_alias( 'TMDO_Listing_Stats', 'WPDO_Listing_Stats' );
|
||||
}
|
||||
|
||||
/**
|
||||
* The WooCommerce product group core registered before 1.2.0 (PR-077 moved
|
||||
* product meta back to native only). Tests that use products as a routed
|
||||
* post type register it themselves; the table name wp_itest_wpdo_post_wc_product
|
||||
* is unchanged.
|
||||
*/
|
||||
function tmdo_test_register_wc_product_group(): void {
|
||||
$enum = static fn( string $key, array $options ): array => array(
|
||||
'key' => $key,
|
||||
'type' => 'enum',
|
||||
'options' => $options,
|
||||
);
|
||||
TMDO_Entity_Registry::register_group(
|
||||
'post',
|
||||
'wc_product',
|
||||
array(
|
||||
array( 'key' => '_price', 'type' => 'decimal', 'searchable' => true ),
|
||||
array( 'key' => '_regular_price', 'type' => 'decimal', 'searchable' => true ),
|
||||
array( 'key' => '_sale_price', 'type' => 'decimal', 'searchable' => true ),
|
||||
array( 'key' => '_stock', 'type' => 'integer', 'searchable' => true ),
|
||||
array( 'key' => '_stock_status', 'type' => 'enum', 'searchable' => true, 'options' => array( 'instock', 'outofstock', 'onbackorder' ) ),
|
||||
array( 'key' => '_sku', 'type' => 'text', 'searchable' => true ),
|
||||
$enum( '_manage_stock', array( 'yes', 'no' ) ),
|
||||
$enum( '_backorders', array( 'yes', 'no', 'notify' ) ),
|
||||
$enum( '_sold_individually', array( 'yes', 'no' ) ),
|
||||
$enum( '_virtual', array( 'yes', 'no' ) ),
|
||||
$enum( '_downloadable', array( 'yes', 'no' ) ),
|
||||
array( 'key' => '_tax_class', 'type' => 'text' ),
|
||||
$enum( '_tax_status', array( 'taxable', 'shipping', 'none' ) ),
|
||||
array( 'key' => '_download_limit', 'type' => 'integer' ),
|
||||
array( 'key' => '_download_expiry', 'type' => 'integer' ),
|
||||
array( 'key' => '_product_version', 'type' => 'text' ),
|
||||
array( 'key' => '_wc_average_rating', 'type' => 'decimal', 'searchable' => true ),
|
||||
array( 'key' => '_wc_review_count', 'type' => 'integer' ),
|
||||
array( 'key' => 'total_sales', 'type' => 'integer', 'searchable' => true ),
|
||||
),
|
||||
array( 'post_types' => array( 'product' ) )
|
||||
);
|
||||
}
|
||||
|
||||
@@ -247,21 +247,22 @@ TMDO_Live::test(
|
||||
TMDO_Live::test(
|
||||
'Q06 LIKE matches substrings and escapes wildcards, routed',
|
||||
static function () {
|
||||
TMDO_Live_Fixtures::field( 'post', '_sku', 'product' );
|
||||
// A text key of wp_core (WooCommerce's _sku left the flat tables in 1.2.0).
|
||||
TMDO_Live_Fixtures::field( 'post', '_wp_page_template' );
|
||||
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
|
||||
$p = tmdo_live_router_posts(
|
||||
'product',
|
||||
'post',
|
||||
array(
|
||||
'P1' => array( '_sku' => 'tmdo-live-B1-1' ),
|
||||
'P2' => array( '_sku' => 'tmdo-live-B1-10' ),
|
||||
'P3' => array( '_sku' => 'tmdo-live-B2-1' ),
|
||||
'P1' => array( '_wp_page_template' => 'tmdo-live-B1-1' ),
|
||||
'P2' => array( '_wp_page_template' => 'tmdo-live-B1-10' ),
|
||||
'P3' => array( '_wp_page_template' => 'tmdo-live-B2-1' ),
|
||||
)
|
||||
);
|
||||
foreach ( array( 'B1-1', 'B1_1' ) as $needle ) {
|
||||
tmdo_live_router_assert_native(
|
||||
array(
|
||||
'post_type' => 'product',
|
||||
'meta_query' => array( array( 'key' => '_sku', 'value' => $needle, 'compare' => 'LIKE' ) ),
|
||||
'post_type' => 'post',
|
||||
'meta_query' => array( array( 'key' => '_wp_page_template', 'value' => $needle, 'compare' => 'LIKE' ) ),
|
||||
),
|
||||
array_values( $p ),
|
||||
true
|
||||
|
||||
@@ -33,13 +33,12 @@ class PostFieldsRegistrationTest extends TestCase {
|
||||
$this->assertTrue( true );
|
||||
}
|
||||
|
||||
public function test_all_four_groups_are_registered(): void {
|
||||
public function test_the_core_groups_are_registered(): void {
|
||||
WPDO_Post_Fields::register_entity_fields();
|
||||
|
||||
$expected = array(
|
||||
'wp_core',
|
||||
'attachment',
|
||||
'wc_product',
|
||||
'nav_menu_item',
|
||||
);
|
||||
|
||||
@@ -92,38 +91,14 @@ class PostFieldsRegistrationTest extends TestCase {
|
||||
$this->assertSame( 'json', $field['type'] );
|
||||
}
|
||||
|
||||
// ── wc_product group ─────────────────────────────────────────────────────
|
||||
// ── WooCommerce products stay in native meta (2.2 PR-077) ───────────────
|
||||
|
||||
public function test_wc_product_group_has_19_keys(): void {
|
||||
public function test_core_registers_no_woocommerce_product_group(): void {
|
||||
WPDO_Post_Fields::register_entity_fields();
|
||||
$keys = $this->get_field_keys( 'wc_product' );
|
||||
$this->assertCount( 19, $keys, 'wc_product group should register exactly 19 keys.' );
|
||||
}
|
||||
|
||||
public function test_wc_product_critical_keys_present(): void {
|
||||
WPDO_Post_Fields::register_entity_fields();
|
||||
$keys = $this->get_field_keys( 'wc_product' );
|
||||
foreach ( array( '_price', '_regular_price', '_sale_price', '_stock', '_stock_status', '_sku' ) as $key ) {
|
||||
$this->assertContains( $key, $keys, "wc_product missing critical key: {$key}" );
|
||||
}
|
||||
}
|
||||
|
||||
public function test_wc_product_price_is_searchable_decimal(): void {
|
||||
WPDO_Post_Fields::register_entity_fields();
|
||||
$field = $this->find_field( 'wc_product', '_price' );
|
||||
$this->assertNotNull( $field );
|
||||
$this->assertSame( 'decimal', $field['type'] );
|
||||
$this->assertTrue( (bool) ( $field['searchable'] ?? false ) );
|
||||
}
|
||||
|
||||
public function test_wc_product_stock_status_is_enum_searchable(): void {
|
||||
WPDO_Post_Fields::register_entity_fields();
|
||||
$field = $this->find_field( 'wc_product', '_stock_status' );
|
||||
$this->assertNotNull( $field );
|
||||
$this->assertSame( 'enum', $field['type'] );
|
||||
$this->assertTrue( (bool) ( $field['searchable'] ?? false ) );
|
||||
$this->assertContains( 'instock', $field['options'] );
|
||||
$this->assertContains( 'outofstock', $field['options'] );
|
||||
$this->assertSame( array(), WPDO_Entity_Registry::get_group_fields( 'post', 'wc_product' ), 'WooCommerce reads and writes product meta directly (stock by SQL)' );
|
||||
$this->assertNull( WPDO_Entity_Registry::get_field( 'post', '_price', 'product' ) );
|
||||
$this->assertSame( array(), WPDO_Post_Fields::groups_for_post_type( 'product' ) );
|
||||
}
|
||||
|
||||
// ── HivePress groups moved to the HivePress AddOn ───────────────────────
|
||||
@@ -139,7 +114,6 @@ class PostFieldsRegistrationTest extends TestCase {
|
||||
|
||||
public function test_core_groups_map_to_their_post_types(): void {
|
||||
$this->assertSame( 'attachment', WPDO_Post_Fields::post_type_for_group( 'attachment' ) );
|
||||
$this->assertSame( 'product', WPDO_Post_Fields::post_type_for_group( 'wc_product' ) );
|
||||
$this->assertSame( 'nav_menu_item', WPDO_Post_Fields::post_type_for_group( 'nav_menu_item' ) );
|
||||
$this->assertNull( WPDO_Post_Fields::post_type_for_group( 'wp_core' ), 'wp_core spans every post type' );
|
||||
}
|
||||
@@ -149,7 +123,6 @@ class PostFieldsRegistrationTest extends TestCase {
|
||||
|
||||
$this->assertSame( 'x_type', WPDO_Post_Fields::post_type_for_group( 'x_core' ) );
|
||||
$this->assertSame( array( 'x_core' ), WPDO_Post_Fields::groups_for_post_type( 'x_type' ) );
|
||||
$this->assertSame( array( 'wc_product' ), WPDO_Post_Fields::groups_for_post_type( 'product' ) );
|
||||
$this->assertNull( WPDO_Post_Fields::post_type_for_group( 'hp_listing_core' ), 'unregistered HivePress group is unknown to core' );
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user