From faa66d8145a7ea12879d089b46b3e3f42c0e4792 Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Mon, 20 Apr 2015 04:45:12 +0000 Subject: [PATCH] WPDB: When sanity checking read queries, there are some collations we can skip, for improved performance. Props pento, nacin. See #21212. git-svn-id: https://develop.svn.wordpress.org/trunk@32162 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/wp-db.php | 68 ++++++++++++++++++++++++++++++ tests/phpunit/tests/db/charset.php | 63 +++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/src/wp-includes/wp-db.php b/src/wp-includes/wp-db.php index 08fc3e5565..cdb9eda8c8 100644 --- a/src/wp-includes/wp-db.php +++ b/src/wp-includes/wp-db.php @@ -170,6 +170,16 @@ class wpdb { */ protected $check_current_query = true; + /** + * Flag to ensure we don't run into recursion problems when checking the collation. + * + * @since 4.2.0 + * @access private + * @see wpdb::check_safe_collation() + * @var boolean + */ + private $checking_collation = false; + /** * Saved info on the table column * @@ -2038,6 +2048,10 @@ class wpdb { public function get_var( $query = null, $x = 0, $y = 0 ) { $this->func_call = "\$db->get_var(\"$query\", $x, $y)"; + if ( $this->check_safe_collation( $query ) ) { + $this->check_current_query = false; + } + if ( $query ) { $this->query( $query ); } @@ -2066,6 +2080,11 @@ class wpdb { */ public function get_row( $query = null, $output = OBJECT, $y = 0 ) { $this->func_call = "\$db->get_row(\"$query\",$output,$y)"; + + if ( $this->check_safe_collation( $query ) ) { + $this->check_current_query = false; + } + if ( $query ) { $this->query( $query ); } else { @@ -2103,6 +2122,10 @@ class wpdb { * @return array Database query result. Array indexed from 0 by SQL result row number. */ public function get_col( $query = null , $x = 0 ) { + if ( $this->check_safe_collation( $query ) ) { + $this->check_current_query = false; + } + if ( $query ) { $this->query( $query ); } @@ -2131,6 +2154,10 @@ class wpdb { public function get_results( $query = null, $output = OBJECT ) { $this->func_call = "\$db->get_results(\"$query\", $output)"; + if ( $this->check_safe_collation( $query ) ) { + $this->check_current_query = false; + } + if ( $query ) { $this->query( $query ); } else { @@ -2358,6 +2385,47 @@ class wpdb { return false; } + /** + * Check if the query is accessing a collation considered safe on the current version of MySQL. + * + * @since 4.2.0 + * @access protected + * + * @param string $query The query to check. + * @return bool True if the collation is safe, false if it isn't. + */ + protected function check_safe_collation( $query ) { + if ( $this->checking_collation ) { + return true; + } + + $table = $this->get_table_from_query( $query ); + if ( ! $table ) { + return false; + } + + $this->checking_collation = true; + $this->get_table_charset( $table ); + $this->checking_collation = false; + + $table = strtolower( $table ); + if ( empty( $this->col_meta[ $table ] ) ) { + return false; + } + + foreach( $this->col_meta[ $table ] as $col ) { + if ( empty( $col->Collation ) ) { + continue; + } + + if ( ! in_array( $col->Collation, array( 'utf8_general_ci', 'utf8_bin', 'utf8mb4_general_ci', 'utf8mb4_bin' ), true ) ) { + return false; + } + } + + return true; + } + /** * Strips any invalid characters based on value/charset pairs. * diff --git a/tests/phpunit/tests/db/charset.php b/tests/phpunit/tests/db/charset.php index 6c886c378b..dc5f428917 100755 --- a/tests/phpunit/tests/db/charset.php +++ b/tests/phpunit/tests/db/charset.php @@ -463,4 +463,67 @@ class Tests_DB_Charset extends WP_UnitTestCase { $this->assertFalse( $wpdb->query( "INSERT INTO {$wpdb->posts} (post_content) VALUES ('foo\xf0\xff\xff\xffbar')" ) ); } + + /** + * @ticket 21212 + */ + function data_table_collation_check() { + $table_name = 'table_collation_check'; + $data = array( + array( + // utf8_bin tables don't need extra sanity checking. + "( a VARCHAR(50) COLLATE utf8_bin )", // create + true // expected result + ), + array( + // Neither do utf8_general_ci tables. + "( a VARCHAR(50) COLLATE utf8_general_ci )", + true + ), + array( + // utf8_unicode_ci tables do. + "( a VARCHAR(50) COLLATE utf8_unicode_ci )", + false + ), + array( + // utf8_bin tables don't need extra sanity checking, + // except for when they're not just utf8_bin. + "( a VARCHAR(50) COLLATE utf8_bin, b VARCHAR(50) COLLATE big5_chinese_ci )", + false + ), + array( + // utf8_bin tables don't need extra sanity checking + // when the other columns aren't strings. + "( a VARCHAR(50) COLLATE utf8_bin, b INT )", + true + ), + ); + + foreach( $data as &$value ) { + $this_table_name = $table_name . '_' . rand_str( 5 ); + + $value[0] = "CREATE TABLE $this_table_name {$value[0]}"; + $value[2] = "SELECT * FROM $this_table_name"; + $value[3] = "DROP TABLE IF EXISTS $this_table_name"; + } + unset( $value ); + + return $data; + } + + + /** + * @dataProvider data_table_collation_check + * @ticket 21212 + */ + function test_table_collation_check( $create, $expected, $query, $drop ) { + self::$_wpdb->query( $drop ); + + self::$_wpdb->query( $create ); + + $return = self::$_wpdb->check_safe_collation( $query ); + $this->assertEquals( $expected, $return ); + + self::$_wpdb->query( $drop ); + } }