fix(2meet-data-optimizer): join each routed group and drop the redundant GROUP BY

- posts_join joins the flat table of every routed group (a wp_core key next to a post type group no longer fails with "Unknown column")
- Flat tables are one row per post (UNIQUE post_id): remove the posts_groupby hook; skip the INNER JOIN status/date copy route for NOT EXISTS, which must match posts without a flat row
- Verify: tests/live/run.php router (9/9); qbench Q05 475 == native, no SQL error; price filter EXPLAIN has no Using temporary
- PR: PR-061
This commit is contained in:
2026-09-26 19:47:53 +08:00
parent b338925605
commit f2e261c522
5 changed files with 113 additions and 55 deletions
+22 -45
View File
@@ -16,16 +16,18 @@
* Hook order:
* 1. pre_get_posts — strip registered keys from meta_query, store in
* $query->set( 'wpdo_post_clauses', $routed )
* 2. posts_join — LEFT JOIN wp_wpdo_post_<group> per routed post_type
* 2. posts_join — LEFT JOIN wp_wpdo_post_<group> per routed group
* 3. posts_where — append WHERE conditions targeting flat columns
* 4. posts_groupby — ensure GROUP BY posts.ID to dedup join multiplication
*
* Flat tables hold one row per post (UNIQUE post_id), so the joins never
* multiply rows and no GROUP BY is added.
*
* Status / date on the flat table (O-1): when the group's post_status /
* post_date copies are ready (TMDO_Post_Status_Sync) and none is missing,
* a single-post-type search with explicit statuses and date ordering
* INNER JOINs the table, filters the copies, orders by the copied
* post_date and skips GROUP BY (one row per post), so MySQL can walk
* `idx_post_status_date` and stop at the LIMIT.
* INNER JOINs the table, filters the copies and orders by the copied
* post_date, so MySQL can walk `idx_post_status_date` and stop at the
* LIMIT. Not with NOT EXISTS: posts without a flat row must still match.
*
* Found rows (O-4): routed searches drop SQL_CALC_FOUND_ROWS (it counts every
* match inside the page query); found_posts_query answers from a COUNT of the
@@ -85,7 +87,6 @@ class TMDO_Post_Query_Router {
add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ), 1000, 1 );
add_filter( 'posts_join', array( $this, 'posts_join' ), 10, 2 );
add_filter( 'posts_where', array( $this, 'posts_where' ), 10, 2 );
add_filter( 'posts_groupby', array( $this, 'posts_groupby' ), 10, 2 );
add_filter( 'posts_orderby', array( $this, 'posts_orderby' ), 10, 2 );
add_filter( 'posts_request', array( $this, 'posts_request' ), 10, 2 );
add_filter( 'found_posts_query', array( $this, 'found_posts_query' ), 10, 2 );
@@ -175,7 +176,7 @@ class TMDO_Post_Query_Router {
}
/**
* LEFT JOIN flat tables for each routed post_type.
* LEFT JOIN the flat table of each routed group.
*
* @param string|null $join Current JOIN SQL.
* @param \WP_Query $query Current WP_Query.
@@ -191,23 +192,20 @@ class TMDO_Post_Query_Router {
global $wpdb;
foreach ( $routed as $post_type => $clauses ) {
// Each routed clause carries its group; use the first clause's group
// (all clauses for a given post_type currently target one group).
$group = $clauses[0]['group'] ?? '';
if ( '' === $group ) {
continue;
}
$table = $wpdb->prefix . 'wpdo_post_' . sanitize_key( $group );
$alias = self::table_alias( $post_type, $group );
foreach ( array_unique( array_column( $clauses, 'group' ) ) as $group ) {
$table = $wpdb->prefix . 'wpdo_post_' . sanitize_key( $group );
$alias = self::table_alias( $post_type, $group );
// Don't double-join.
if ( false !== strpos( $join, "`{$alias}`" ) ) {
continue;
}
// Don't double-join.
if ( false !== strpos( $join, "`{$alias}`" ) ) {
continue;
}
$type = self::copy_route_for( $query, $post_type ) ? 'INNER' : 'LEFT';
$join .= " {$type} JOIN `{$table}` AS `{$alias}` "
. "ON (`{$wpdb->posts}`.`ID` = `{$alias}`.`post_id`)";
// The copy route only applies to single-group searches.
$type = self::copy_route_for( $query, $post_type ) ? 'INNER' : 'LEFT';
$join .= " {$type} JOIN `{$table}` AS `{$alias}` "
. "ON (`{$wpdb->posts}`.`ID` = `{$alias}`.`post_id`)";
}
}
return $join;
@@ -265,28 +263,6 @@ class TMDO_Post_Query_Router {
return '`' . self::table_alias( $copy['post_type'], $copy['group'] ) . '`.`post_date` ' . $copy['order'];
}
/**
* Ensure GROUP BY posts.ID to deduplicate JOIN-multiplied rows.
*
* @param string|null $groupby Current GROUP BY SQL.
* @param \WP_Query $query Current WP_Query.
* @return string Modified GROUP BY SQL.
*/
public function posts_groupby( ?string $groupby, \WP_Query $query ): string {
$groupby = (string) ( $groupby ?? '' );
$routed = $query->get( self::QUERY_VAR );
if ( empty( $routed ) || ! is_array( $routed ) || ! empty( $query->get( self::COPY_VAR ) ) ) {
return $groupby;
}
global $wpdb;
if ( '' === trim( $groupby ) ) {
$groupby = "`{$wpdb->posts}`.`ID`";
}
return $groupby;
}
/**
* Drop SQL_CALC_FOUND_ROWS from a routed search; keep its COUNT for later.
*
@@ -365,7 +341,8 @@ class TMDO_Post_Query_Router {
}
$post_type = (string) array_key_first( $routed );
$groups = array_values( array_unique( array_column( $routed[ $post_type ], 'group' ) ) );
if ( 1 !== count( $groups ) || ! in_array( $query->get( 'orderby' ), array( '', 'date', 'post_date' ), true ) ) {
if ( 1 !== count( $groups ) || ! in_array( $query->get( 'orderby' ), array( '', 'date', 'post_date' ), true )
|| in_array( 'NOT EXISTS', array_column( $routed[ $post_type ], 'compare' ), true ) ) {
return array();
}
$statuses = $query->get( 'post_status' );