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

CANTINA-852: do not alter meta_key index for large tables #2893

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
77 changes: 69 additions & 8 deletions schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,81 @@
global $wpdb;
foreach ( $queries as $k => $q ) {
// Replace meta_key index with one that indexes meta_value as well
if ( preg_match( '|CREATE TABLE ([^ ]*)|', $q, $matches ) && $wpdb->postmeta === $matches[1] ) {
$queries[ $k ] = str_replace(
'KEY meta_key (meta_key(191))',
sprintf( 'KEY %s', get_postmeta_key_value_index() ),
$q
);
if ( false !== strpos( $q, "CREATE TABLE {$wpdb->postmeta}" ) ) {
$search = 'KEY meta_key (meta_key(191))';
if ( false !== strpos( $q, $search ) ) {
$status = _get_table_status( $wpdb->postmeta );
/* If the table does not exist, simply replace the index definition */
if ( null === $status ) {
$queries[ $k ] = str_replace( $search, get_postmeta_key_value_index( 'meta_key' ), $q );
} else {
/* The table exists, we need to check the index */
$indices = _get_table_indices( $wpdb->postmeta );
if ( isset( $indices['meta_key'] ) ) {
/* If meta_key index covers both meta_key and meta_value, modify meta_key index definition to match */
if ( get_postmeta_key_value_index( 'meta_key' ) === $indices['meta_key'] ) { // NOSONAR
$queries[ $k ] = str_replace( $search, get_postmeta_key_value_index( 'meta_key' ), $q );
}
/* Otherwise, do nothing, rely upon a cron job to fix this */
}
}
}
}
}

return $queries;
});

function get_postmeta_key_value_index() {
function _get_table_status( string $table ): ?array {
global $wpdb;

// phpcs:ignore WordPress.DB.DirectDatabaseQuery
return $wpdb->get_row( $wpdb->prepare( 'SHOW TABLE STATUS WHERE Name = %s', $table ), ARRAY_A );
}

function _get_table_indices( string $table ): array {
global $wpdb;
return _parse_indices( $wpdb->get_results( "SHOW INDEX FROM {$table}", ARRAY_A ) ); // phpcs:ignore WordPress.DB
}

function _parse_indices( array $rows ): array {
$result = [];
$flags = [];
$keys = [];
foreach ( $rows as $row ) {
$is_primary = 'PRIMARY' === $row['Key_name'];
$is_unique = ! $is_primary && '0' === $row['Non_unique'];
$is_fulltext = 'FULLTEXT' === $row['Index_type'];
$index_name = $row['Key_name'];
$column = $row['Column_name'];
$sequence = (int) $row['Seq_in_index'];
$prefix = null === $row['Sub_part'] ? null : (int) $row['Sub_part'];

if ( isset( $keys[ $index_name ] ) ) {
$keys[ $index_name ][ $sequence ] = sprintf( '`%s`%s', $column, null === $prefix ? '' : "({$prefix})" );
} else {
$keys[ $index_name ] = [
$sequence => sprintf( '`%s`%s', $column, null === $prefix ? '' : "({$prefix})" ),
];

$flags[ $index_name ] = $is_primary ? 'PRIMARY KEY' : ( $is_unique ? 'UNIQUE KEY' : ( $is_fulltext ? 'FULLTEXT KEY' : 'KEY' ) ); // NOSONAR
}
}

foreach ( $keys as $name => $columns ) {
$columns = join( ', ', $columns );
if ( 'PRIMARY' === $name ) {
$result[ $name ] = 'PRIMARY KEY (' . $columns . ')';
} else {
$result[ $name ] = sprintf( '%s `%s` (%s)', $flags[ $name ], $name, $columns );
}
}

return $result;
}

function get_postmeta_key_value_index( string $index_name ) {
// 191 for meta_key is max set by core.
// 100 for meta_value is arbitrary-ish.
return '`vip_meta_key_value` (`meta_key`(191), `meta_value`(100))';
return sprintf( 'KEY `%s` (`meta_key`(191), `meta_value`(100))', $index_name );
}
236 changes: 234 additions & 2 deletions tests/test-schema.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,244 @@
<?php

use function Automattic\VIP\Schema\_parse_indices;

require_once ABSPATH . '/wp-admin/includes/upgrade.php';

class VIP_Go_Schema_Test extends WP_UnitTestCase {
public function test__dbDelta__verify_blog_tables() {
$deltas = dbDelta( 'blog', false ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.dbDelta_dbdelta

$this->assertCount( 1, $deltas, 'More deltas than expected for blogs table' );
$this->assertEquals( $deltas[0], 'Added index wptests_postmeta KEY `vip_meta_key_value` (`meta_key`(191),`meta_value`(100))', 'Delta did not find vip_meta_key_value index or assertion needs updating.' );
$this->assertCount( 0, $deltas );
}

public function test__get_table_indices(): void {
$indices = [
[
'Table' => 'test',
'Non_unique' => '0',
'Key_name' => 'PRIMARY',
'Seq_in_index' => '1',
'Column_name' => 'id',
'Collation' => 'A',
'Cardinality' => '242386',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'active',
'Seq_in_index' => '1',
'Column_name' => 'active',
'Collation' => 'A',
'Cardinality' => '4',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'active',
'Seq_in_index' => '2',
'Column_name' => 'last_modified',
'Collation' => 'A',
'Cardinality' => '121193',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'last_modified',
'Seq_in_index' => '1',
'Column_name' => 'last_modified',
'Collation' => 'A',
'Cardinality' => '80795',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'name',
'Seq_in_index' => '1',
'Column_name' => 'name',
'Collation' => 'A',
'Cardinality' => '242386',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'name',
'Seq_in_index' => '2',
'Column_name' => 'active',
'Collation' => 'A',
'Cardinality' => '242386',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'address',
'Seq_in_index' => '1',
'Column_name' => 'address',
'Collation' => 'A',
'Cardinality' => '242386',
'Sub_part' => '255',
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'address',
'Seq_in_index' => '2',
'Column_name' => 'active',
'Collation' => 'A',
'Cardinality' => '242386',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'country',
'Seq_in_index' => '1',
'Column_name' => 'country',
'Collation' => 'A',
'Cardinality' => '682',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'country',
'Seq_in_index' => '2',
'Column_name' => 'active',
'Collation' => 'A',
'Cardinality' => '708',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'status',
'Seq_in_index' => '1',
'Column_name' => 'status',
'Collation' => 'A',
'Cardinality' => '12',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'slug',
'Seq_in_index' => '1',
'Column_name' => 'slug',
'Collation' => 'A',
'Cardinality' => '242386',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'slug',
'Seq_in_index' => '2',
'Column_name' => 'status',
'Collation' => 'A',
'Cardinality' => '242386',
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
],
[
'Table' => 'test',
'Non_unique' => '1',
'Key_name' => 'description',
'Seq_in_index' => '1',
'Column_name' => 'description',
'Collation' => null,
'Cardinality' => null,
'Sub_part' => null,
'Packed' => null,
'Null' => '',
'Index_type' => 'FULLTEXT',
'Comment' => '',
'Index_comment' => '',
],
];

$expected = [
'PRIMARY' => 'PRIMARY KEY (`id`)',
'active' => 'KEY `active` (`active`, `last_modified`)',
'last_modified' => 'KEY `last_modified` (`last_modified`)',
'name' => 'KEY `name` (`name`, `active`)',
'address' => 'KEY `address` (`address`(255), `active`)',
'country' => 'KEY `country` (`country`, `active`)',
'status' => 'KEY `status` (`status`)',
'slug' => 'KEY `slug` (`slug`, `status`)',
'description' => 'FULLTEXT KEY `description` (`description`)',
];

$actual = _parse_indices( $indices );

self::assertEquals( $expected, $actual );
}
}