Skip to content

Commit 88912df

Browse files
Database: Ignore default integer display widths in dbDelta() on MariaDB.
`dbDelta()` compares the column type in a `CREATE TABLE` query with the type `DESCRIBE` reports. MariaDB, and MySQL before 8.0.17, add a default display width to an integer column declared without one, so a column declared as `bigint unsigned` reports `bigint(20) unsigned`, and `dbDelta()` sends the same `ALTER TABLE` on every call. Likewise, `INTEGER` never matches the `int` the server reports. This commit skips the change when the query has no width and the server reports exactly the default width for that type, and treats `INTEGER` as `int`; any other difference is still compared, and MySQL 8.0.17+ keeps its existing code path, similar to the 6.1 change in #49364. Developed in WordPress#14353. Follow-up to r53897, r60789. Props jmdodd, janjakes, SergeyBiryukov. Fixes #46145. git-svn-id: https://develop.svn.wordpress.org/trunk@64487 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 518cd65 commit 88912df

2 files changed

Lines changed: 257 additions & 5 deletions

File tree

‎src/wp-admin/includes/upgrade.php‎

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3030,6 +3030,33 @@ function dbDelta( $queries = '', $execute = true ) { // phpcs:ignore WordPress.N
30303030
$blob_fields = array( 'tinyblob', 'blob', 'mediumblob', 'longblob' );
30313031
$int_fields = array( 'tinyint', 'smallint', 'mediumint', 'int', 'integer', 'bigint' );
30323032

3033+
/*
3034+
* Display widths that a server applies by itself when an integer definition omits one.
3035+
* MariaDB does this in every version, as did MySQL before 8.0.17.
3036+
*/
3037+
$int_display_widths = array(
3038+
'tinyint' => array(
3039+
'signed' => 4,
3040+
'unsigned' => 3,
3041+
),
3042+
'smallint' => array(
3043+
'signed' => 6,
3044+
'unsigned' => 5,
3045+
),
3046+
'mediumint' => array(
3047+
'signed' => 9,
3048+
'unsigned' => 8,
3049+
),
3050+
'int' => array(
3051+
'signed' => 11,
3052+
'unsigned' => 10,
3053+
),
3054+
'bigint' => array(
3055+
'signed' => 20,
3056+
'unsigned' => 20,
3057+
),
3058+
);
3059+
30333060
$global_tables = $wpdb->tables( 'global' );
30343061
$db_version = $wpdb->db_version();
30353062
$db_server_info = $wpdb->db_server_info();
@@ -3208,6 +3235,12 @@ function dbDelta( $queries = '', $execute = true ) { // phpcs:ignore WordPress.N
32083235
$fieldtype = $matches[1];
32093236
$fieldtype_lowercased = strtolower( $fieldtype );
32103237

3238+
/*
3239+
* `INTEGER` is an alias for `INT`, and the server reports the column as `int`,
3240+
* so the alias never compares equal to the type it created.
3241+
*/
3242+
$fieldtype_lowercased = preg_replace( '/^integer\b/', 'int', $fieldtype_lowercased );
3243+
32113244
$fieldtype_without_parentheses = preg_replace(
32123245
'/'
32133246
. '(.+)' // Field type, e.g. `int`.
@@ -3239,15 +3272,31 @@ function dbDelta( $queries = '', $execute = true ) { // phpcs:ignore WordPress.N
32393272
if ( in_array( $fieldtype_base, $int_fields, true ) && in_array( $tablefield_type_base, $int_fields, true )
32403273
&& $fieldtype_without_parentheses === $tablefield_type_without_parentheses
32413274
) {
3242-
/*
3243-
* MySQL 8.0.17 or later does not support display width for integer data types,
3244-
* so if display width is the only difference, it can be safely ignored.
3245-
* Note: This is specific to MySQL and does not affect MariaDB.
3246-
*/
32473275
if ( version_compare( $db_version, '8.0.17', '>=' )
32483276
&& ! str_contains( $db_server_info, 'MariaDB' )
32493277
) {
3278+
/*
3279+
* MySQL 8.0.17 or later does not support display width for integer data types,
3280+
* so if display width is the only difference, it can be safely ignored.
3281+
* Note: This is specific to MySQL and does not affect MariaDB.
3282+
*/
32503283
$do_change = false;
3284+
} elseif ( ! str_contains( $fieldtype_lowercased, '(' )
3285+
&& isset( $int_display_widths[ $fieldtype_base ] )
3286+
) {
3287+
/*
3288+
* Every other server stores a default display width when the definition
3289+
* omits one, so `DESCRIBE` reports a width the query never asked for.
3290+
* The `ALTER TABLE` below cannot remove that width, which would leave the
3291+
* same difference in place and repeat the query on every call.
3292+
*/
3293+
$signedness = str_contains( $fieldtype_lowercased, 'unsigned' ) ? 'unsigned' : 'signed';
3294+
$default_width = $int_display_widths[ $fieldtype_base ][ $signedness ];
3295+
$padded_type = $fieldtype_base . '(' . $default_width . ')' . substr( $fieldtype_lowercased, strlen( $fieldtype_base ) );
3296+
3297+
if ( $tablefield_type_lowercased === $padded_type ) {
3298+
$do_change = false;
3299+
}
32513300
}
32523301
}
32533302

‎tests/phpunit/tests/db/dbDelta.php‎

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,209 @@ public function test_column_type_change() {
230230
);
231231
}
232232

233+
/**
234+
* Tests that an integer column declared without a display width converges.
235+
*
236+
* @ticket 46145
237+
*/
238+
public function test_integer_column_without_display_width_does_not_cause_an_alter_query() {
239+
240+
global $wpdb;
241+
242+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_int_test" );
243+
244+
$schema =
245+
"
246+
CREATE TABLE {$wpdb->prefix}dbdelta_int_test (
247+
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
248+
signed_bigint BIGINT NOT NULL,
249+
signed_int INT NOT NULL,
250+
unsigned_int INT UNSIGNED NOT NULL,
251+
signed_mediumint MEDIUMINT NOT NULL,
252+
unsigned_mediumint MEDIUMINT UNSIGNED NOT NULL,
253+
signed_smallint SMALLINT NOT NULL,
254+
unsigned_smallint SMALLINT UNSIGNED NOT NULL,
255+
signed_tinyint TINYINT NOT NULL,
256+
unsigned_tinyint TINYINT UNSIGNED NOT NULL,
257+
PRIMARY KEY (id)
258+
) {$this->db_engine};
259+
";
260+
261+
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
262+
$wpdb->query( $schema );
263+
264+
$updates = dbDelta( $schema, false );
265+
266+
$this->assertEmpty( $updates );
267+
268+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_int_test" );
269+
}
270+
271+
/**
272+
* Tests that the same schema converges on a second pass, not just the first.
273+
*
274+
* @ticket 46145
275+
*/
276+
public function test_integer_column_without_display_width_converges_after_an_alter_query() {
277+
278+
global $wpdb;
279+
280+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_int_test" );
281+
282+
$create =
283+
"
284+
CREATE TABLE {$wpdb->prefix}dbdelta_int_test (
285+
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
286+
counter SMALLINT UNSIGNED NOT NULL,
287+
PRIMARY KEY (id)
288+
) {$this->db_engine};
289+
";
290+
291+
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
292+
$wpdb->query( $create );
293+
294+
$schema =
295+
"
296+
CREATE TABLE {$wpdb->prefix}dbdelta_int_test (
297+
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
298+
counter INT UNSIGNED NOT NULL,
299+
PRIMARY KEY (id)
300+
) {$this->db_engine};
301+
";
302+
303+
dbDelta( $schema );
304+
305+
$updates = dbDelta( $schema, false );
306+
307+
$this->assertEmpty( $updates );
308+
309+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_int_test" );
310+
}
311+
312+
/**
313+
* Tests that `INTEGER` matches the `int` the server reports it as.
314+
*
315+
* @ticket 46145
316+
*/
317+
public function test_integer_alias_does_not_cause_an_alter_query() {
318+
319+
global $wpdb;
320+
321+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_int_test" );
322+
323+
$schema =
324+
"
325+
CREATE TABLE {$wpdb->prefix}dbdelta_int_test (
326+
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
327+
counter INTEGER NOT NULL,
328+
PRIMARY KEY (id)
329+
) {$this->db_engine};
330+
";
331+
332+
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
333+
$wpdb->query( $schema );
334+
335+
$updates = dbDelta( $schema, false );
336+
337+
$this->assertEmpty( $updates );
338+
339+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_int_test" );
340+
}
341+
342+
/**
343+
* Tests that a real type change is still detected when the query omits the display width.
344+
*
345+
* @ticket 46145
346+
*/
347+
public function test_integer_column_without_display_width_still_detects_a_type_change() {
348+
349+
global $wpdb;
350+
351+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_int_test" );
352+
353+
$create =
354+
"
355+
CREATE TABLE {$wpdb->prefix}dbdelta_int_test (
356+
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
357+
counter BIGINT NOT NULL,
358+
PRIMARY KEY (id)
359+
) {$this->db_engine};
360+
";
361+
362+
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
363+
$wpdb->query( $create );
364+
365+
$updates = dbDelta(
366+
"
367+
CREATE TABLE {$wpdb->prefix}dbdelta_int_test (
368+
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
369+
counter INT NOT NULL,
370+
PRIMARY KEY (id)
371+
) {$this->db_engine};
372+
",
373+
false
374+
);
375+
376+
$this->assertSame(
377+
array(
378+
"{$wpdb->prefix}dbdelta_int_test.counter",
379+
),
380+
array_keys( $updates )
381+
);
382+
383+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_int_test" );
384+
}
385+
386+
/**
387+
* Tests that a non-default display width in the table is still corrected.
388+
*
389+
* @ticket 46145
390+
*/
391+
public function test_integer_column_with_non_default_display_width_still_causes_an_alter_query() {
392+
393+
global $wpdb;
394+
395+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_int_test" );
396+
397+
if ( version_compare( self::$db_version, '8.0.17', '>=' )
398+
&& ! str_contains( self::$db_server_info, 'MariaDB' )
399+
) {
400+
$this->markTestSkipped( 'MySQL 8.0.17 or later does not report display width for integer data types.' );
401+
}
402+
403+
$create =
404+
"
405+
CREATE TABLE {$wpdb->prefix}dbdelta_int_test (
406+
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
407+
counter INT(5) NOT NULL,
408+
PRIMARY KEY (id)
409+
) {$this->db_engine};
410+
";
411+
412+
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
413+
$wpdb->query( $create );
414+
415+
$updates = dbDelta(
416+
"
417+
CREATE TABLE {$wpdb->prefix}dbdelta_int_test (
418+
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
419+
counter INT NOT NULL,
420+
PRIMARY KEY (id)
421+
) {$this->db_engine};
422+
",
423+
false
424+
);
425+
426+
$this->assertSame(
427+
array(
428+
"{$wpdb->prefix}dbdelta_int_test.counter",
429+
),
430+
array_keys( $updates )
431+
);
432+
433+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}dbdelta_int_test" );
434+
}
435+
233436
/**
234437
* Test new column added.
235438
*/

0 commit comments

Comments
 (0)