namespace Yoast\WP\Lib\Migrations;
* Yoast migrations adapter class.
* The version of this adapter.
private $version = '1.0';
* Whether or not a transaction has been started.
private $in_transaction = false;
* Returns the current database name.
public function get_database_name() {
* Checks support for migrations.
public function supports_migrations() {
* Returns all column native types.
public function native_database_types() {
'text' => [ 'name' => 'text' ],
'tinytext' => [ 'name' => 'tinytext' ],
'mediumtext' => [ 'name' => 'mediumtext' ],
'tinyinteger' => [ 'name' => 'tinyint' ],
'smallinteger' => [ 'name' => 'smallint' ],
'mediuminteger' => [ 'name' => 'mediumint' ],
'biginteger' => [ 'name' => 'bigint' ],
'float' => [ 'name' => 'float' ],
'datetime' => [ 'name' => 'datetime' ],
'timestamp' => [ 'name' => 'timestamp' ],
'time' => [ 'name' => 'time' ],
'date' => [ 'name' => 'date' ],
'binary' => [ 'name' => 'blob' ],
'tinybinary' => [ 'name' => 'tinyblob' ],
'mediumbinary' => [ 'name' => 'mediumblob' ],
'longbinary' => [ 'name' => 'longblob' ],
'char' => [ 'name' => 'char' ],
* Checks if a table exists.
* @param string $table The table name.
public function has_table( $table ) {
return $this->table_exists( $table );
* Allows overriding the hardcoded schema table name constant in case of parallel migrations.
public function get_schema_version_table_name() {
return Model::get_table_name( 'migrations' );
* Create the schema table, if necessary.
public function create_schema_version_table() {
if ( ! $this->has_table( $this->get_schema_version_table_name() ) ) {
$t = $this->create_table( $this->get_schema_version_table_name() );
$t->column( 'version', 'string', [ 'limit' => 191 ] );
$this->add_index( $this->get_schema_version_table_name(), 'version', [ 'unique' => true ] );
public function start_transaction() {
if ( $this->in_transaction() === false ) {
$this->begin_transaction();
public function commit_transaction() {
if ( $this->in_transaction() ) {
* Rollbacks a transaction.
public function rollback_transaction() {
if ( $this->in_transaction() ) {
* Quotes a table name string.
* @param string $string Table name.
public function quote_table( $string ) {
return '`' . $string . '`';
* Return the SQL definition of a column.
* @param string $column_name The column name.
* @param string $type The type of the column.
* @param array|null $options Column options.
public function column_definition( $column_name, $type, $options = null ) {
$col = new Column( $this, $column_name, $type, $options );
return $col->__toString();
* Checks if a database exists.
* @param string $database The database name.
public function database_exists( $database ) {
$result = $this->select_all( $ddl );
if ( \count( $result ) === 0 ) {
foreach ( $result as $dbrow ) {
if ( $dbrow['Database'] === $database ) {
* @param string $db The database name.
public function create_database( $db ) {
if ( $this->database_exists( $db ) ) {
$ddl = \sprintf( 'CREATE DATABASE %s', $this->identifier( $db ) );
$result = $this->query( $ddl );
* @param string $db The database name.
public function drop_database( $db ) {
if ( ! $this->database_exists( $db ) ) {
$ddl = \sprintf( 'DROP DATABASE IF EXISTS %s', $this->identifier( $db ) );
$result = $this->query( $ddl );
* Checks if a table exists.
* @param string $table The table name.
public function table_exists( $table ) {
// We need last error to be clear so we can check against it easily.
$previous_last_error = $wpdb->last_error;
$previous_suppress_errors = $wpdb->suppress_errors;
$wpdb->suppress_errors = true;
$result = $wpdb->query( "SELECT * FROM $table LIMIT 1" );
// Restore the last error, as this is not truly an error and we don't want to alarm people.
$wpdb->last_error = $previous_last_error;
$wpdb->suppress_errors = $previous_suppress_errors;
return $result !== false;
* Wrapper to execute a query.
* @param string $query The query to run.
public function execute( $query ) {
return $this->query( $query );
* @param string $query The query to run.
* @return bool Whether or not the query was performed succesfully.
public function query( $query ) {
$query_type = $this->determine_query_type( $query );
if ( $query_type === Constants::SQL_SELECT || $query_type === Constants::SQL_SHOW ) {
$data = $wpdb->get_results( $query, ARRAY_A );
// INSERT, DELETE, etc...
$result = $wpdb->query( $query );
if ( $result === false ) {
if ( $query_type === Constants::SQL_INSERT ) {
* Returns a single result for a query.
* @param string $query The query to run.
* @return array|false An associative array of the result.
public function select_one( $query ) {
$query_type = $this->determine_query_type( $query );
if ( $query_type === Constants::SQL_SELECT || $query_type === Constants::SQL_SHOW ) {
$result = $wpdb->query( $query );
if ( $result === false ) {
return $wpdb->last_result[0];
* Returns all results for a query.
* @param string $query The query to run.
* @return array An array of associative arrays.
public function select_all( $query ) {
return $this->query( $query );
* Use this method for non-SELECT queries.
* Or anything where you dont necessarily expect a result string, e.g. DROPs, CREATEs, etc.
* @param string $ddl The query to run.
public function execute_ddl( $ddl ) {
return $this->query( $ddl );
* @param string $table The table name.
* @return bool Whether or not the table was succesfully dropped.
public function drop_table( $table ) {
$ddl = \sprintf( 'DROP TABLE IF EXISTS %s', $this->identifier( $table ) );
return $this->query( $ddl );
* @param string $table_name The table name.
* @param array $options The options.
public function create_table( $table_name, $options = [] ) {
return new Table( $this, $table_name, $options );
* Escapes a string for usage in queries.
* @param string $string The string.
public function quote_string( $string ) {
return $wpdb->_escape( $string );
* Returns a quoted string.
* @param string $string The string.
public function identifier( $string ) {
return '`' . $string . '`';
* @param string $name The current table name.
* @param string $new_name The new table name.
public function rename_table( $name, $new_name ) {
if ( empty( $name ) || empty( $new_name ) ) {
$sql = \sprintf( 'RENAME TABLE %s TO %s', $this->identifier( $name ), $this->identifier( $new_name ) );
return $this->execute_ddl( $sql );
* @param string $table_name The table name.
* @param string $column_name The column name.
* @param string $type The column type.
* @param array $options Column options.
public function add_column( $table_name, $column_name, $type, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) || empty( $type ) ) {
if ( ! \array_key_exists( 'limit', $options ) ) {
$options['limit'] = null;
if ( ! \array_key_exists( 'precision', $options ) ) {
$options['precision'] = null;
if ( ! \array_key_exists( 'scale', $options ) ) {
$options['scale'] = null;
$sql = \sprintf( 'ALTER TABLE %s ADD `%s` %s', $this->identifier( $table_name ), $column_name, $this->type_to_sql( $type, $options ) );
$sql .= $this->add_column_options( $type, $options );
return $this->execute_ddl( $sql );
* @param string $table_name The table name.
* @param string $column_name The column name.
public function remove_column( $table_name, $column_name ) {
$sql = \sprintf( 'ALTER TABLE %s DROP COLUMN %s', $this->identifier( $table_name ), $this->identifier( $column_name ) );
return $this->execute_ddl( $sql );
* @param string $table_name The table name.
* @param string $column_name The column name.
* @param string $new_column_name The new column name.
public function rename_column( $table_name, $column_name, $new_column_name ) {
if ( empty( $table_name ) || empty( $column_name ) || empty( $new_column_name ) ) {
$column_info = $this->column_info( $table_name, $column_name );
$current_type = $column_info['type'];
$sql = \sprintf( 'ALTER TABLE %s CHANGE %s %s %s', $this->identifier( $table_name ), $this->identifier( $column_name ), $this->identifier( $new_column_name ), $current_type );
$sql .= $this->add_column_options( $current_type, $column_info );
return $this->execute_ddl( $sql );
* @param string $table_name The table name.
* @param string $column_name The column name.
* @param string $type The column type.
* @param array $options Column options.