Skip to content
Merged
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
82 changes: 82 additions & 0 deletions tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,88 @@ public function testDefaultValueEscaping(): void {
);
}

public function testInvalidDataTypeCacheDataForDecimalDefinition(): void {
// Recreate the invalid database state before the following fix:
// https://github.com/WordPress/sqlite-database-integration/pull/126
$connection = $this->engine->get_connection();
$connection->query( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL );
$connection->query( 'CREATE TABLE t ( dec_col REAL )' );
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'decimal', 'decimal(26,')" );

// Ensure the information schema is reconstructed correctly.
$this->reconstructor->ensure_correct_information_schema();
$result = $this->assertQuery( 'SHOW CREATE TABLE t' );
$this->assertSame(
implode(
"\n",
array(
'CREATE TABLE `t` (',
' `dec_col` float DEFAULT NULL',
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci',
)
),
$result[0]->{'Create Table'}
);
}

public function testInvalidDataTypeCacheDataForDecimalDefinitionOnWooCommerceTable(): void {
// Recreate the invalid database state before the following fix:
// https://github.com/WordPress/sqlite-database-integration/pull/126
$connection = $this->engine->get_connection();
$connection->query( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL );
$connection->query( 'CREATE TABLE wc_testing_table ( col1 REAL, col2 REAL, col3 REAL, col4 REAL )' );
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('wc_testing_table', 'col1', 'decimal(26,')" );
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('wc_testing_table', 'col2', 'decimal(19,')" );
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('wc_testing_table', 'col3', 'decimal(3,')" );
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('wc_testing_table', 'col4', 'decimal(5,')" );

// Ensure the information schema is reconstructed correctly.
$this->reconstructor->ensure_correct_information_schema();
$result = $this->assertQuery( 'SHOW CREATE TABLE wc_testing_table' );
$this->assertSame(
implode(
"\n",
array(
'CREATE TABLE `wc_testing_table` (',
' `col1` decimal(26,8) DEFAULT NULL,',
' `col2` decimal(19,4) DEFAULT NULL,',
' `col3` decimal(3,2) DEFAULT NULL,',
' `col4` float DEFAULT NULL',
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci',
)
),
$result[0]->{'Create Table'}
);
}

public function testInvalidDataTypeCacheDataForIndexDefinition(): void {
$connection = $this->engine->get_connection();

// Recreate the invalid database state before the following fix:
// https://github.com/WordPress/sqlite-database-integration/commit/b5a9fbaed4d0d843f792aaa959e3d00f193ff1ee
// https://github.com/Automattic/sqlite-database-integration/pull/2
$connection->query( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL );
$connection->query( 'CREATE TABLE t ( `timestamp` TEXT, `KEY` TEXT)' );
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'timestamp', 'datetime')" );
$connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'KEY', 'timestamp(timestamp)')" );

// Ensure the information schema is reconstructed correctly.
$this->reconstructor->ensure_correct_information_schema();
$result = $this->assertQuery( 'SHOW CREATE TABLE t' );
$this->assertSame(
implode(
"\n",
array(
'CREATE TABLE `t` (',
' `timestamp` datetime DEFAULT NULL,',
' `KEY` text DEFAULT NULL',
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci',
)
),
$result[0]->{'Create Table'}
);
}

private function assertQuery( $sql ) {
$retval = $this->engine->query( $sql );
$this->assertNotFalse( $retval );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,72 @@ private function get_cached_mysql_data_type( string $table_name, string $column_
return null;
}

/**
* Check whether the stored type value is a valid MySQL column type.
*
* Some older versions of the legacy SQLite driver might have stored
* invalid MySQL column types in some scenarios:
*
* 1. Before https://github.com/WordPress/sqlite-database-integration/pull/126,
* the legacy SQLite driver incorrectly stored MySQL column types
* for columns with multiple type arguments.
*
* E.g., a column definition like "col_name decimal(26, 8)" would
* be stored with invalid type "decimal(26,".
*
* 2. Before https://github.com/WordPress/sqlite-database-integration/commit/b5a9fbaed4d0d843f792aaa959e3d00f193ff1ee
* (see also https://github.com/Automattic/sqlite-database-integration/pull/2),
* the legacy SQLite driver incorrectly recognized indexes on columns
* with type keywords as additional table column definitions.
*
* E.g., an index definition like "KEY timestamp (timestamp)" would
* be stored as column "KEY" with invalid type "timestamp(timestamp)".
*
* To address these issues, we need to check whether the stored type looks
* like a valid MySQL column type definition.
*/
$open_par_index = strpos( $mysql_type, '(' );
$close_par_index = strpos( $mysql_type, ')' );
if ( false !== $open_par_index ) {
$end = false !== $close_par_index ? $close_par_index : strlen( $mysql_type );
$parts = explode( '(', substr( $mysql_type, 0, $end ) );
$type = strtolower( trim( $parts[0] ) );
$args = array();
foreach ( explode( ',', $parts[1] ) as $arg ) {
$args[] = strtolower( trim( $arg ) );
}

// WooCommerce uses decimal(26,8), decimal(19,4), and decimal(3,2)
// column types, so we can can fix the invalid column definitions.
$looks_like_wc_table = str_contains( $table_name, 'wc_' ) || str_contains( $table_name, 'woocommerce_' );
$is_invalid_decimal = 'decimal' === $type && count( $args ) === 2 && '' === $args[1];
if ( $looks_like_wc_table && $is_invalid_decimal ) {
if ( '26' === $args[0] ) {
// Fix "decimal(26,".
return 'decimal(26,8)';
} elseif ( '19' === $args[0] ) {
// Fix "decimal(19,".
return 'decimal(19,4)';
} elseif ( '3' === $args[0] ) {
// Fix "decimal(3,".
return 'decimal(3,2)';
}
}

// Only numeric arguments are allowed for MySQL column types.
// This handles the incorrectly stored index definition case.
foreach ( $args as $arg ) {
if ( ! is_numeric( $arg ) ) {
return null;
}
}

// If there is no closing parenthesis, the type is invalid.
if ( false === $close_par_index ) {
return null;
}
}

// Normalize index type for backward compatibility. Some older versions
// of the SQLite driver stored index types with a " KEY" suffix, e.g.,
// "PRIMARY KEY" or "UNIQUE KEY". More recent versions omit the suffix.
Expand Down
Loading