driver = $driver; $this->schema_builder = $schema_builder; $this->schema_reconstructor = new WP_SQLite_Information_Schema_Reconstructor( $driver, $schema_builder ); } /** * Ensure that the SQLite database is configured. * * This method checks if the database is configured for the latest SQLite * driver version, and if it is not, it will configure the database. */ public function ensure_database_configured(): void { $version = SQLITE_DRIVER_VERSION; $db_version = $this->driver->get_saved_driver_version(); if ( version_compare( $version, $db_version ) > 0 ) { $this->configure_database(); } } /** * Configure the SQLite database. * * This method creates tables used for emulating MySQL behaviors in SQLite, * and populates them with necessary data. When it is used with an already * configured database, it will update the configuration as per the current * SQLite driver version and attempt to repair any configuration corruption. */ public function configure_database(): void { // Use an EXCLUSIVE transaction to prevent multiple connections // from attempting to configure the database at the same time. $this->driver->execute_sqlite_query( 'BEGIN EXCLUSIVE TRANSACTION' ); try { $this->ensure_global_variables_table(); $this->schema_builder->ensure_information_schema_tables(); $this->schema_reconstructor->ensure_correct_information_schema(); $this->save_current_driver_version(); $this->ensure_database_data(); } catch ( Throwable $e ) { $this->driver->execute_sqlite_query( 'ROLLBACK' ); throw $e; } $this->driver->execute_sqlite_query( 'COMMIT' ); } /** * Ensure that the global variables table exists. * * This method configures a database table to store MySQL global variables * and other internal configuration values. */ private function ensure_global_variables_table(): void { $this->driver->execute_sqlite_query( sprintf( 'CREATE TABLE IF NOT EXISTS %s (name TEXT PRIMARY KEY, value TEXT)', $this->driver->get_connection()->quote_identifier( WP_PDO_MySQL_On_SQLite::GLOBAL_VARIABLES_TABLE_NAME ) ) ); } /** * Ensure that the database data is correctly populated. * * This method ensures that the "INFORMATION_SCHEMA.SCHEMATA" table contains * records for both the "INFORMATION_SCHEMA" database and the user database. * At the moment, only a single user database is supported. * * Additionally, this method ensures that the user database name is stored * correctly in all the information schema tables. */ public function ensure_database_data(): void { // Get all databases from the "SCHEMATA" table. $schemata_table = $this->schema_builder->get_table_name( false, 'schemata' ); $databases = $this->driver->execute_sqlite_query( sprintf( 'SELECT SCHEMA_NAME FROM %s', $this->driver->get_connection()->quote_identifier( $schemata_table ) ) )->fetchAll( PDO::FETCH_COLUMN ); // phpcs:disable WordPress.DB.RestrictedClasses.mysql__PDO // Ensure that the "INFORMATION_SCHEMA" database record exists. if ( ! in_array( 'information_schema', $databases, true ) ) { $this->driver->execute_sqlite_query( sprintf( 'INSERT INTO %s (SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME) VALUES (?, ?, ?)', $this->driver->get_connection()->quote_identifier( $schemata_table ) ), // The "INFORMATION_SCHEMA" database stays on "utf8mb3" even in MySQL 8 and 9. array( 'information_schema', 'utf8mb3', 'utf8mb3_general_ci' ) ); } // Get the existing user database name. $existing_user_db_name = null; foreach ( $databases as $database ) { if ( 'information_schema' !== strtolower( $database ) ) { $existing_user_db_name = $database; break; } } // Ensure that the user database record exists. if ( null === $existing_user_db_name ) { $existing_user_db_name = WP_SQLite_Information_Schema_Builder::SAVED_DATABASE_NAME; $this->driver->execute_sqlite_query( sprintf( 'INSERT INTO %s (SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME) VALUES (?, ?, ?)', $this->driver->get_connection()->quote_identifier( $schemata_table ) ), // @TODO: This should probably be version-dependent. // Before MySQL 8, the default was different. array( $existing_user_db_name, 'utf8mb4', 'utf8mb4_0900_ai_ci' ) ); } // Migrate from older versions without dynamic database names. $saved_database_name = WP_SQLite_Information_Schema_Builder::SAVED_DATABASE_NAME; if ( $saved_database_name !== $existing_user_db_name ) { // INFORMATION_SCHEMA.SCHEMATA $this->driver->execute_sqlite_query( sprintf( "UPDATE %s SET SCHEMA_NAME = ? WHERE SCHEMA_NAME != 'information_schema'", $this->driver->get_connection()->quote_identifier( $schemata_table ) ), array( $saved_database_name ) ); // INFORMATION_SCHEMA.TABLES $tables_table = $this->schema_builder->get_table_name( false, 'tables' ); $this->driver->execute_sqlite_query( sprintf( "UPDATE %s SET TABLE_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'", $this->driver->get_connection()->quote_identifier( $tables_table ) ), array( $saved_database_name ) ); // INFORMATION_SCHEMA.COLUMNS $columns_table = $this->schema_builder->get_table_name( false, 'columns' ); $this->driver->execute_sqlite_query( sprintf( "UPDATE %s SET TABLE_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'", $this->driver->get_connection()->quote_identifier( $columns_table ) ), array( $saved_database_name ) ); // INFORMATION_SCHEMA.STATISTICS $statistics_table = $this->schema_builder->get_table_name( false, 'statistics' ); $this->driver->execute_sqlite_query( sprintf( "UPDATE %s SET TABLE_SCHEMA = ?, INDEX_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'", $this->driver->get_connection()->quote_identifier( $statistics_table ) ), array( $saved_database_name, $saved_database_name ) ); // INFORMATION_SCHEMA.TABLE_CONSTRAINTS $table_constraints_table = $this->schema_builder->get_table_name( false, 'table_constraints' ); $this->driver->execute_sqlite_query( sprintf( "UPDATE %s SET TABLE_SCHEMA = ?, CONSTRAINT_SCHEMA = ? WHERE TABLE_SCHEMA != 'information_schema'", $this->driver->get_connection()->quote_identifier( $table_constraints_table ) ), array( $saved_database_name, $saved_database_name ) ); // INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS $referential_constraints_table = $this->schema_builder->get_table_name( false, 'referential_constraints' ); $this->driver->execute_sqlite_query( sprintf( "UPDATE %s SET CONSTRAINT_SCHEMA = ?, UNIQUE_CONSTRAINT_SCHEMA = ? WHERE CONSTRAINT_SCHEMA != 'information_schema'", $this->driver->get_connection()->quote_identifier( $referential_constraints_table ) ), array( $saved_database_name, $saved_database_name ) ); // INFORMATION_SCHEMA.KEY_COLUMN_USAGE $key_column_usage_table = $this->schema_builder->get_table_name( false, 'key_column_usage' ); $this->driver->execute_sqlite_query( sprintf( "UPDATE %s SET TABLE_SCHEMA = ?, CONSTRAINT_SCHEMA = ?, REFERENCED_TABLE_SCHEMA = CASE WHEN REFERENCED_TABLE_SCHEMA IS NULL THEN NULL ELSE ? END WHERE TABLE_SCHEMA != 'information_schema'", $this->driver->get_connection()->quote_identifier( $key_column_usage_table ) ), array( $saved_database_name, $saved_database_name, $saved_database_name ) ); // INFORMATION_SCHEMA.CHECK_CONSTRAINTS $check_constraints_table = $this->schema_builder->get_table_name( false, 'check_constraints' ); $this->driver->execute_sqlite_query( sprintf( "UPDATE %s SET CONSTRAINT_SCHEMA = ? WHERE CONSTRAINT_SCHEMA != 'information_schema'", $this->driver->get_connection()->quote_identifier( $check_constraints_table ) ), array( $saved_database_name ) ); } } /** * Save the current SQLite driver version. * * This method saves the current SQLite driver version to the database. */ private function save_current_driver_version(): void { $this->driver->execute_sqlite_query( sprintf( 'INSERT INTO %s (name, value) VALUES (?, ?) ON CONFLICT(name) DO UPDATE SET value = ?', $this->driver->get_connection()->quote_identifier( WP_PDO_MySQL_On_SQLite::GLOBAL_VARIABLES_TABLE_NAME ) ), array( WP_PDO_MySQL_On_SQLite::DRIVER_VERSION_VARIABLE_NAME, SQLITE_DRIVER_VERSION, SQLITE_DRIVER_VERSION, ) ); } }