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
Original file line number Diff line number Diff line change
Expand Up @@ -206,16 +206,14 @@ public function validate_schema() {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$database = (string) $this->wpdb->get_var( 'SELECT DATABASE()' );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$engine = $this->wpdb->get_var(
$engine = $this->wpdb->get_var(
$this->wpdb->prepare(
'SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s',
$database,
$this->table_name
)
);
if ( 'INNODB' !== strtoupper( (string) $engine ) ) {
return new \WP_Error( 'identity_schema_engine', 'Post identity reservation table must use InnoDB.' );
}
$uses_innodb = 'INNODB' === strtoupper( (string) $engine );

$required_columns = array(
'identity_hash' => array(
Expand Down Expand Up @@ -324,6 +322,9 @@ public function validate_schema() {
if ( ! $has_nonunique_post_id || $has_unique_post_id ) {
return new \WP_Error( 'identity_schema_post_index', 'Post identity reservation post_id requires a nonunique index.' );
}
if ( ! $uses_innodb && ! $this->supports_transactional_tables( array( $this->table_name, $this->wpdb->posts ) ) ) {
return new \WP_Error( 'identity_schema_engine', 'Post identity reservation table must use InnoDB.' );
}

return true;
}
Expand Down Expand Up @@ -705,6 +706,9 @@ protected function verify_transactional_storage() {
return new \WP_Error( 'identity_storage_invalid', 'Post identity storage has an invalid table name.' );
}
}
if ( $this->supports_transactional_tables( $tables ) ) {
return true;
}

$placeholders = implode( ',', array_fill( 0, count( $tables ), '%s' ) );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
Expand All @@ -729,6 +733,24 @@ protected function verify_transactional_storage() {
return true;
}

/**
* Ask an optional database implementation to prove one atomic table set.
*
* The capability is deliberately exact: it may replace the InnoDB engine
* check only when every table in this reservation operation participates.
*/
private function supports_transactional_tables( array $tables ): bool {
if ( ! method_exists( $this->wpdb, 'supports_transactional_tables' ) ) {
return false;
}

try {
return true === $this->wpdb->supports_transactional_tables( $tables );
} catch ( \Throwable ) {
return false;
}
}

/** @return int[] */
protected function find_legacy_candidates( array $identity ): array {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,10 @@ public function test_missing_table_fails_identity_write_closed(): void {
}
}

public function test_nontransactional_reservation_table_fails_closed(): void {
public function test_nontransactional_reservation_table_fails_closed_without_the_capability(): void {
global $wpdb;

$this->assertFalse( method_exists( $wpdb, 'supports_transactional_tables' ) );
$changed = $wpdb->query( $wpdb->prepare( 'ALTER TABLE %i ENGINE=MyISAM', $this->repository->get_table_name() ) );
$this->assertNotFalse( $changed );
try {
Expand All @@ -138,6 +139,80 @@ public function test_nontransactional_reservation_table_fails_closed(): void {
}
}

/** This fake wpdb covers consumer routing, not native capability conformance. */
public function test_exact_transactional_table_capability_routes_only_a_complete_valid_schema(): void {
global $wpdb;

$this->assertNotFalse( $wpdb->query( $wpdb->prepare( 'ALTER TABLE %i ENGINE=MyISAM', $this->repository->get_table_name() ) ) );
$original = $wpdb;
$capable = new class( $original ) extends \wpdb {
private \wpdb $delegate;
public array $supported_tables = array();
public mixed $capability_result = false;

public function __construct( \wpdb $delegate ) {
$this->delegate = $delegate;
$this->prefix = $delegate->prefix;
$this->base_prefix = $delegate->base_prefix;
$this->posts = $delegate->posts;
}

public function prepare( $query, ...$args ) {
return $this->delegate->prepare( $query, ...$args );
}

public function query( $query ) {
return $this->delegate->query( $query );
}

public function get_var( $query = null, $x = 0, $y = 0 ) {
return $this->delegate->get_var( $query, $x, $y );
}

public function get_results( $query = null, $output = OBJECT ) {
return $this->delegate->get_results( $query, $output );
}

public function supports_transactional_tables( array $tables ) {
if ( 'throw' === $this->capability_result ) {
throw new \RuntimeException( 'Unsupported transactional table capability.' );
}
return $tables === $this->supported_tables ? $this->capability_result : false;
}
};
$wpdb = $capable;

try {
$repository = new PostIdentityReservations();
$capable->supported_tables = array( $repository->get_table_name() );
$incomplete = $repository->validate_schema();
$this->assertWPError( $incomplete );
$this->assertSame( 'identity_schema_engine', $incomplete->get_error_code() );

$capable->supported_tables = array( $repository->get_table_name(), $capable->posts );
$capable->capability_result = 1;
$strict_false = $repository->validate_schema();
$this->assertWPError( $strict_false );
$this->assertSame( 'identity_schema_engine', $strict_false->get_error_code() );

$capable->capability_result = 'throw';
$thrown = $repository->validate_schema();
$this->assertWPError( $thrown );
$this->assertSame( 'identity_schema_engine', $thrown->get_error_code() );

$capable->capability_result = true;
$this->assertTrue( $repository->validate_schema() );

$wpdb->query( $wpdb->prepare( 'ALTER TABLE %i DROP COLUMN completed_at', $repository->get_table_name() ) );
$invalid_schema = $repository->validate_schema();
$this->assertWPError( $invalid_schema );
$this->assertSame( 'identity_schema_columns', $invalid_schema->get_error_code() );
} finally {
$wpdb = $original;
PostIdentityReservations::create_table();
}
}

public function test_create_table_repairs_myisam_to_innodb(): void {
global $wpdb;

Expand Down
Loading