perf(2meet-data-optimizer): let flat-first sorts use a (column, id) index

- a group index may end with the entity ID column (post_id, ...)
- flat-first rewrites the posts.ID tie-breaks once, in the sort's direction:
  with a (column, post_id) index MySQL returns rows in order instead of
  sorting the whole match. dev36 (1.3M listings), price range sorted by price:
  850 ms filesort -> 2 ms. Ties are ordered by ID the same way as the column
  (natively ID DESC); pagination stays stable, result sets are unchanged
- Verify: EntityRegistryIndexesTest, PostQueryRouterFoundRowsTest (ASC
  tie-break, single tie-break), unit 704, integration 589, phpcs, stan;
  dev36 with the HivePress indexes (PR-119)
- PR: PR-118
This commit is contained in:
2026-09-27 22:40:59 +08:00
parent b2725b219d
commit eac0955b76
4 changed files with 44 additions and 7 deletions
@@ -159,9 +159,12 @@ final class TMDO_Entity_Registry {
self::$scopes[ $entity_type ][ $group_name ] = $post_types;
}
$keys = array_column( $normalized, 'key' );
// A sort index may end with the entity ID (flat-first tie-break, 2.4 PR-118).
$id_col = (string) self::$adapters[ $entity_type ]->get_entity_id_column();
foreach ( (array) ( $args['indexes'] ?? array() ) as $index_name => $columns ) {
$columns = array_values( array_map( 'strval', (array) $columns ) );
if ( $columns && ! array_diff( $columns, $keys ) ) {
$fields = '' !== $id_col && end( $columns ) === $id_col ? array_slice( $columns, 0, -1 ) : $columns;
if ( $fields && ! array_diff( $fields, $keys ) ) {
self::$indexes[ $entity_type ][ $group_name ][ (string) $index_name ] = $columns;
}
}
@@ -797,10 +797,17 @@ class TMDO_Post_Query_Router {
if ( 1 === preg_match_all( '/\bJOIN\b/i', (string) $flat_first ) ) {
$flat_first = preg_replace( '/\s+GROUP BY\s+`?' . preg_quote( $wpdb->posts, '/' ) . '`?\.`?ID`?(?=\s)/', ' ', (string) $flat_first, 1 );
}
// Tie-breaks on posts.ID (ours, and any a plugin appended) read the flat post_id.
// Tie-breaks on posts.ID (ours, and any a plugin appended) read the flat
// post_id, once, in the sort's direction: a (column, post_id) index then
// returns rows in order with no filesort. Ties are ordered by ID the same
// way as the column (natively ID DESC); pagination stays stable.
$tie = "`{$alias}`.`post_id` " . ( 'ASC' === strtoupper( (string) $sort['order'] ) ? 'ASC' : 'DESC' );
$flat_first = preg_replace_callback(
'/\bORDER BY\b(?!.*\bORDER BY\b).*$/s',
static fn( $m ) => preg_replace( '/`?' . preg_quote( $wpdb->posts, '/' ) . '`?\.`?ID`?(?!\w)/', "`{$alias}`.`post_id`", $m[0] ),
static function ( $m ) use ( $wpdb, $tie ) {
$order = (string) preg_replace( '/`?' . preg_quote( $wpdb->posts, '/' ) . '`?\.`?ID`?(?!\w)(\s+(ASC|DESC)\b)?/i', $tie, $m[0] );
return (string) preg_replace( '/(,\s*' . preg_quote( $tie, '/' ) . ')(\s*,\s*' . preg_quote( $tie, '/' ) . ')+/', '$1', $order );
},
(string) $flat_first
);
return (string) preg_replace( '/^\s*SELECT\s/', 'SELECT STRAIGHT_JOIN ', (string) $flat_first, 1 );