Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WP_Query: sort more arrays to improve cache hits. #7497

Open
wants to merge 38 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4d9563c
WP_Query: sort more arrays to improve cache hits.
peterwilsoncc Oct 4, 2024
f3ee673
More sorting of arrays.
peterwilsoncc Oct 4, 2024
3afbbeb
Don’t modify original values for items that can be used for orderby.
peterwilsoncc Oct 4, 2024
35ac6f1
Fix the cat.
peterwilsoncc Oct 4, 2024
3328307
Fix the tag.
peterwilsoncc Oct 4, 2024
e30a3d2
IDs are sorted now.
peterwilsoncc Oct 4, 2024
a826bc0
Additional cache key sorting.
peterwilsoncc Jan 15, 2025
6d6c5d2
Some tests.
peterwilsoncc Jan 16, 2025
3a21ef8
Asseert cache keys differ fro test that cache keys differ.
peterwilsoncc Jan 17, 2025
193c0d4
These fail but I need to figure out why.
peterwilsoncc Jan 17, 2025
0099947
CS.
peterwilsoncc Jan 17, 2025
5bef5d7
If this works, I'll be very confused.
peterwilsoncc Jan 21, 2025
5b56748
Revert "If this works, I'll be very confused."
peterwilsoncc Jan 21, 2025
3dcc07a
Test the cache key generated by WP_Query.
peterwilsoncc Jan 22, 2025
a5c32f8
Cat not in test.
peterwilsoncc Jan 22, 2025
fd46cca
Sort authors.
peterwilsoncc Jan 22, 2025
760c496
Test authors
peterwilsoncc Jan 22, 2025
62c9181
phpcbf
peterwilsoncc Jan 22, 2025
11b2ea5
Test and sort category__and.
peterwilsoncc Jan 22, 2025
a0b22f1
Sort tags.
peterwilsoncc Jan 22, 2025
9f06cb0
Fix author in test.
peterwilsoncc Jan 22, 2025
db407f2
tag tests
peterwilsoncc Jan 22, 2025
8b23a08
phpcbf
peterwilsoncc Jan 22, 2025
2ad2f8b
phpcbf
peterwilsoncc Jan 22, 2025
794269e
numeric string tests for `p`, `page_id`, `attachment_id`, date/time a…
peterwilsoncc Jan 28, 2025
697dc8a
Numeric string vs int for authors.
peterwilsoncc Jan 28, 2025
6751a42
cat numeric vs string
peterwilsoncc Jan 28, 2025
39483b3
menu_order numeric vs string.
peterwilsoncc Jan 28, 2025
754b242
Tests for non-unique vs unique arrays.
peterwilsoncc Jan 28, 2025
b9b6458
Ensure author_in array is unnique.
peterwilsoncc Jan 28, 2025
caefb34
Further tests of non-unique arrays.
peterwilsoncc Jan 28, 2025
569dcbd
unique version should be unique.
peterwilsoncc Jan 28, 2025
fe9deb7
Normalise author not in arrays.
peterwilsoncc Jan 28, 2025
1e0c8ad
Rename and add additional tests.
peterwilsoncc Jan 28, 2025
609d879
Ensure post type array is unique.
peterwilsoncc Jan 28, 2025
ae82b51
Ensure post status is unique.
peterwilsoncc Jan 28, 2025
5a775b5
Further non-unique array tests.
peterwilsoncc Jan 28, 2025
62db25a
Ensure where clauses are unique for post*__in.
peterwilsoncc Jan 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 95 additions & 17 deletions src/wp-includes/class-wp-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,16 @@ class WP_Query {

private $compat_methods = array( 'init_query_flags', 'parse_tax_query' );

/**
* The cache key generated by the query.
*
* The cache key is generated by the method ::generate_cache_key() after the
* query has been normalized.
*
* @var string
*/
private $query_cache_key = '';

/**
* Resets query flags to false.
*
Expand Down Expand Up @@ -1101,15 +1111,17 @@ public function parse_query( $query = '' ) {

if ( ! empty( $qv['post_type'] ) ) {
if ( is_array( $qv['post_type'] ) ) {
$qv['post_type'] = array_map( 'sanitize_key', $qv['post_type'] );
$qv['post_type'] = array_map( 'sanitize_key', array_unique( $qv['post_type'] ) );
sort( $qv['post_type'] );
} else {
$qv['post_type'] = sanitize_key( $qv['post_type'] );
}
}

if ( ! empty( $qv['post_status'] ) ) {
if ( is_array( $qv['post_status'] ) ) {
$qv['post_status'] = array_map( 'sanitize_key', $qv['post_status'] );
$qv['post_status'] = array_map( 'sanitize_key', array_unique( $qv['post_status'] ) );
sort( $qv['post_status'] );
} else {
$qv['post_status'] = preg_replace( '|[^a-z0-9_,-]|', '', $qv['post_status'] );
}
Expand Down Expand Up @@ -1182,9 +1194,12 @@ public function parse_tax_query( &$q ) {

$term = $q[ $t->query_var ];

if ( is_array( $term ) ) {
$term = implode( ',', $term );
if ( ! is_array( $term ) ) {
$term = explode( ',', $term );
$term = array_map( 'trim', $term );
}
sort( $term );
$term = implode( ',', $term );

if ( str_contains( $term, '+' ) ) {
$terms = preg_split( '/[+]+/', $term );
Expand Down Expand Up @@ -1220,7 +1235,8 @@ public function parse_tax_query( &$q ) {

$cat_array = preg_split( '/[,\s]+/', urldecode( $q['cat'] ) );
$cat_array = array_map( 'intval', $cat_array );
$q['cat'] = implode( ',', $cat_array );
sort( $cat_array );
$q['cat'] = implode( ',', $cat_array );

foreach ( $cat_array as $cat ) {
if ( $cat > 0 ) {
Expand Down Expand Up @@ -1262,7 +1278,8 @@ public function parse_tax_query( &$q ) {

if ( ! empty( $q['category__in'] ) ) {
$q['category__in'] = array_map( 'absint', array_unique( (array) $q['category__in'] ) );
$tax_query[] = array(
sort( $q['category__in'] );
$tax_query[] = array(
'taxonomy' => 'category',
'terms' => $q['category__in'],
'field' => 'term_id',
Expand All @@ -1272,6 +1289,7 @@ public function parse_tax_query( &$q ) {

if ( ! empty( $q['category__not_in'] ) ) {
$q['category__not_in'] = array_map( 'absint', array_unique( (array) $q['category__not_in'] ) );
sort( $q['category__not_in'] );
$tax_query[] = array(
'taxonomy' => 'category',
'terms' => $q['category__not_in'],
Expand All @@ -1282,7 +1300,8 @@ public function parse_tax_query( &$q ) {

if ( ! empty( $q['category__and'] ) ) {
$q['category__and'] = array_map( 'absint', array_unique( (array) $q['category__and'] ) );
$tax_query[] = array(
sort( $q['category__and'] );
$tax_query[] = array(
'taxonomy' => 'category',
'terms' => $q['category__and'],
'field' => 'term_id',
Expand All @@ -1300,10 +1319,12 @@ public function parse_tax_query( &$q ) {

if ( '' !== $q['tag'] && ! $this->is_singular && $this->query_vars_changed ) {
if ( str_contains( $q['tag'], ',' ) ) {
// @todo Handle normalizing `tag` query string.
joemcgill marked this conversation as resolved.
Show resolved Hide resolved
$tags = preg_split( '/[,\r\n\t ]+/', $q['tag'] );
foreach ( (array) $tags as $tag ) {
$tag = sanitize_term_field( 'slug', $tag, 0, 'post_tag', 'db' );
$q['tag_slug__in'][] = $tag;
sort( $q['tag_slug__in'] );
}
} elseif ( preg_match( '/[+\r\n\t ]+/', $q['tag'] ) || ! empty( $q['cat'] ) ) {
$tags = preg_split( '/[+\r\n\t ]+/', $q['tag'] );
Expand All @@ -1314,6 +1335,7 @@ public function parse_tax_query( &$q ) {
} else {
$q['tag'] = sanitize_term_field( 'slug', $q['tag'], 0, 'post_tag', 'db' );
$q['tag_slug__in'][] = $q['tag'];
sort( $q['tag_slug__in'] );
}
}

Expand All @@ -1327,14 +1349,16 @@ public function parse_tax_query( &$q ) {

if ( ! empty( $q['tag__in'] ) ) {
$q['tag__in'] = array_map( 'absint', array_unique( (array) $q['tag__in'] ) );
$tax_query[] = array(
sort( $q['tag__in'] );
$tax_query[] = array(
'taxonomy' => 'post_tag',
'terms' => $q['tag__in'],
);
}

if ( ! empty( $q['tag__not_in'] ) ) {
$q['tag__not_in'] = array_map( 'absint', array_unique( (array) $q['tag__not_in'] ) );
sort( $q['tag__not_in'] );
$tax_query[] = array(
'taxonomy' => 'post_tag',
'terms' => $q['tag__not_in'],
Expand All @@ -1344,7 +1368,8 @@ public function parse_tax_query( &$q ) {

if ( ! empty( $q['tag__and'] ) ) {
$q['tag__and'] = array_map( 'absint', array_unique( (array) $q['tag__and'] ) );
$tax_query[] = array(
sort( $q['tag__and'] );
$tax_query[] = array(
'taxonomy' => 'post_tag',
'terms' => $q['tag__and'],
'operator' => 'AND',
Expand All @@ -1353,7 +1378,8 @@ public function parse_tax_query( &$q ) {

if ( ! empty( $q['tag_slug__in'] ) ) {
$q['tag_slug__in'] = array_map( 'sanitize_title_for_query', array_unique( (array) $q['tag_slug__in'] ) );
$tax_query[] = array(
sort( $q['tag_slug__in'] );
$tax_query[] = array(
'taxonomy' => 'post_tag',
'terms' => $q['tag_slug__in'],
'field' => 'slug',
Expand All @@ -1362,7 +1388,8 @@ public function parse_tax_query( &$q ) {

if ( ! empty( $q['tag_slug__and'] ) ) {
$q['tag_slug__and'] = array_map( 'sanitize_title_for_query', array_unique( (array) $q['tag_slug__and'] ) );
$tax_query[] = array(
sort( $q['tag_slug__and'] );
$tax_query[] = array(
'taxonomy' => 'post_tag',
'terms' => $q['tag_slug__and'],
'field' => 'slug',
Expand Down Expand Up @@ -2186,8 +2213,11 @@ public function get_posts() {
$where .= " AND {$wpdb->posts}.post_name = '" . $q['attachment'] . "'";
} elseif ( is_array( $q['post_name__in'] ) && ! empty( $q['post_name__in'] ) ) {
$q['post_name__in'] = array_map( 'sanitize_title_for_query', $q['post_name__in'] );
$post_name__in = "'" . implode( "','", $q['post_name__in'] ) . "'";
$where .= " AND {$wpdb->posts}.post_name IN ($post_name__in)";
// Duplicate array before sorting to allow for the orderby clause.
$post_name__in_for_where = array_unique( $q['post_name__in'] );
sort( $post_name__in_for_where );
$post_name__in = "'" . implode( "','", $post_name__in_for_where ) . "'";
$where .= " AND {$wpdb->posts}.post_name IN ($post_name__in)";
}

// If an attachment is requested by number, let it supersede any post number.
Expand All @@ -2199,19 +2229,29 @@ public function get_posts() {
if ( $q['p'] ) {
$where .= " AND {$wpdb->posts}.ID = " . $q['p'];
} elseif ( $q['post__in'] ) {
$post__in = implode( ',', array_map( 'absint', $q['post__in'] ) );
// Duplicate array before sorting to allow for the orderby clause.
$post__in_for_where = $q['post__in'];
$post__in_for_where = array_unique( array_map( 'absint', $post__in_for_where ) );
sort( $post__in_for_where );
$post__in = implode( ',', array_map( 'absint', $post__in_for_where ) );
$where .= " AND {$wpdb->posts}.ID IN ($post__in)";
} elseif ( $q['post__not_in'] ) {
sort( $q['post__not_in'] );
$post__not_in = implode( ',', array_map( 'absint', $q['post__not_in'] ) );
$where .= " AND {$wpdb->posts}.ID NOT IN ($post__not_in)";
}

if ( is_numeric( $q['post_parent'] ) ) {
$where .= $wpdb->prepare( " AND {$wpdb->posts}.post_parent = %d ", $q['post_parent'] );
} elseif ( $q['post_parent__in'] ) {
$post_parent__in = implode( ',', array_map( 'absint', $q['post_parent__in'] ) );
// Duplicate array before sorting to allow for the orderby clause.
$post_parent__in_for_where = $q['post_parent__in'];
$post_parent__in_for_where = array_unique( array_map( 'absint', $post_parent__in_for_where ) );
sort( $post_parent__in_for_where );
$post_parent__in = implode( ',', array_map( 'absint', $post_parent__in_for_where ) );
$where .= " AND {$wpdb->posts}.post_parent IN ($post_parent__in)";
} elseif ( $q['post_parent__not_in'] ) {
sort( $q['post_parent__not_in'] );
$post_parent__not_in = implode( ',', array_map( 'absint', $q['post_parent__not_in'] ) );
$where .= " AND {$wpdb->posts}.post_parent NOT IN ($post_parent__not_in)";
}
Expand Down Expand Up @@ -2341,6 +2381,7 @@ public function get_posts() {
if ( ! empty( $q['author'] ) && '0' != $q['author'] ) {
$q['author'] = addslashes_gpc( '' . urldecode( $q['author'] ) );
$authors = array_unique( array_map( 'intval', preg_split( '/[,\s]+/', $q['author'] ) ) );
sort( $authors );
foreach ( $authors as $author ) {
$key = $author > 0 ? 'author__in' : 'author__not_in';
$q[ $key ][] = abs( $author );
Expand All @@ -2349,9 +2390,17 @@ public function get_posts() {
}

if ( ! empty( $q['author__not_in'] ) ) {
$author__not_in = implode( ',', array_map( 'absint', array_unique( (array) $q['author__not_in'] ) ) );
if ( is_array( $q['author__not_in'] ) ) {
$q['author__not_in'] = array_unique( array_map( 'absint', $q['author__not_in'] ) );
sort( $q['author__not_in'] );
}
$author__not_in = implode( ',', (array) $q['author__not_in'] );
$where .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) ";
} elseif ( ! empty( $q['author__in'] ) ) {
if ( is_array( $q['author__in'] ) ) {
$q['author__in'] = array_unique( array_map( 'absint', $q['author__in'] ) );
sort( $q['author__in'] );
}
$author__in = implode( ',', array_map( 'absint', array_unique( (array) $q['author__in'] ) ) );
$where .= " AND {$wpdb->posts}.post_author IN ($author__in) ";
}
Expand Down Expand Up @@ -2588,6 +2637,7 @@ public function get_posts() {
if ( ! is_array( $q_status ) ) {
$q_status = explode( ',', $q_status );
}
sort( $q_status );
$r_status = array();
$p_status = array();
$e_status = array();
Expand Down Expand Up @@ -4902,6 +4952,33 @@ protected function generate_cache_key( array $args, $sql ) {
// Sort post types to ensure same cache key generation.
sort( $args['post_type'] );

/*
* Sort arrays that can be used for ordering prior to cache key generation.
*
* These arrays are sorted in the query generator for the purposes of the
* WHERE clause but the arguments are not modified as they can be used for
* the orderby clase.
*
* Their use in the orderby clause will generate a different SQL query so
* they can be sorted for the cache key generation.
*/
$sortable_arrays_with_int_values = array(
'post__in',
'post_parent__in',
);
foreach ( $sortable_arrays_with_int_values as $key ) {
if ( isset( $args[ $key ] ) && is_array( $args[ $key ] ) ) {
$args[ $key ] = array_unique( array_map( 'absint', $args[ $key ] ) );
sort( $args[ $key ] );
}
}

// Sort and unique the 'post_name__in' for cache key generation.
if ( isset( $args['post_name__in'] ) && is_array( $args['post_name__in'] ) ) {
$args['post_name__in'] = array_unique( $args['post_name__in'] );
sort( $args['post_name__in'] );
}

if ( isset( $args['post_status'] ) ) {
$args['post_status'] = (array) $args['post_status'];
// Sort post status to ensure same cache key generation.
Expand Down Expand Up @@ -4942,7 +5019,8 @@ static function ( &$value ) use ( $wpdb, $placeholder ) {
$last_changed .= wp_cache_get_last_changed( 'terms' );
}

return "wp_query:$key:$last_changed";
$this->query_cache_key = "wp_query:$key:$last_changed";
return $this->query_cache_key;
}

/**
Expand Down
Loading
Loading