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:
@@ -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' );
|
||||
|
||||
Reference in New Issue
Block a user