220 lines
6.5 KiB
PHP
220 lines
6.5 KiB
PHP
<?php
|
|
|
|
/**
|
|
* A model to manage database changes
|
|
*
|
|
* @author Time.ly Network Inc.
|
|
* @since 2.0
|
|
*
|
|
* @package AI1EC
|
|
* @subpackage AI1EC.Database
|
|
*/
|
|
class Ai1ec_Database_Applicator extends Ai1ec_Base {
|
|
|
|
/**
|
|
* @var Ai1ec_Dbi Instance of wpdb object
|
|
*/
|
|
protected $_db = NULL;
|
|
|
|
/**
|
|
* @var Ai1ec_Database Instance of Ai1ec_Database object
|
|
*/
|
|
protected $_database = NULL;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* Initialize object, by storing instance of `wpdb` in local variable
|
|
*
|
|
* @return void Constructor does not return
|
|
*/
|
|
public function __construct( Ai1ec_Registry_Object $registry ) {
|
|
parent::__construct( $registry );
|
|
$this->_db = $registry->get( 'dbi.dbi' );
|
|
$this->_database = $registry->get( 'database.helper' );
|
|
}
|
|
|
|
/**
|
|
* remove_instance_duplicates method
|
|
*
|
|
* Remove duplicate instances, from `event_instances` table
|
|
*
|
|
* @param int $depth Private argument, denoting number of iterations to
|
|
* try, before reverting to slow approach
|
|
*
|
|
* @return bool Success
|
|
*/
|
|
public function remove_instance_duplicates( $depth = 5 ) {
|
|
$use_field = 'id';
|
|
if ( $depth < 0 ) {
|
|
$use_field = 'post_id';
|
|
}
|
|
$table = $this->_table( 'event_instances' );
|
|
if ( false === $this->_database->table_exists( $table ) ) {
|
|
return true;
|
|
}
|
|
$duplicates = $this->find_duplicates(
|
|
$table,
|
|
$use_field,
|
|
array( 'post_id', 'start' )
|
|
);
|
|
$count = count( $duplicates );
|
|
if ( $count > 0 ) {
|
|
$sql_query = 'DELETE FROM ' . $table .
|
|
' WHERE ' . $use_field . ' IN ( ' .
|
|
implode( ', ', $duplicates ) . ' )';
|
|
$this->_db->query( $sql_query );
|
|
}
|
|
if ( 'post_id' === $use_field ) { // slow branch
|
|
$event_instance_model = $this->_registry->get(
|
|
'model.event.instance'
|
|
);
|
|
foreach ( $duplicates as $post_id ) {
|
|
try {
|
|
$event_instance_model->recreate(
|
|
$this->_registry->get( 'model.event', $post_id )
|
|
);
|
|
} catch ( Ai1ec_Exception $excpt ) {
|
|
// discard any internal errors
|
|
}
|
|
}
|
|
} else if ( $count > 0 ) { // retry
|
|
return $this->remove_instance_duplicates( --$depth );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* find_duplicates method
|
|
*
|
|
* Find a list of duplicates in table, given search key and groupping fields
|
|
*
|
|
* @param string $table Name of table, to search duplicates in
|
|
* @param string $primary Column, to return values for
|
|
* @param array $group List of fields, to group values on
|
|
*
|
|
* @return array List of primary field values
|
|
*/
|
|
public function find_duplicates( $table, $primary, array $group ) {
|
|
$sql_query = '
|
|
SELECT
|
|
MIN( {{primary}} ) AS dup_primary -- pop oldest
|
|
FROM {{table}}
|
|
GROUP BY {{group}}
|
|
HAVING COUNT( {{primary}} ) > 1
|
|
';
|
|
$sql_query = str_replace(
|
|
array(
|
|
'{{table}}',
|
|
'{{primary}}',
|
|
'{{group}}',
|
|
),
|
|
array(
|
|
$this->_table( $table ),
|
|
$this->_escape_column( $primary ),
|
|
implode(
|
|
', ',
|
|
array_map( array( $this, '_escape_column' ), $group )
|
|
),
|
|
),
|
|
$sql_query
|
|
);
|
|
$result = $this->_db->get_col( $sql_query );
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Check list of tables for consistency.
|
|
*
|
|
* @return array List of inconsistencies.
|
|
*/
|
|
public function check_db_consistency_for_date_migration() {
|
|
$db_migration = $this->_registry->get( 'database.datetime-migration' );
|
|
/* @var $db_migration Ai1ecdm_Datetime_Migration */
|
|
$tables = $db_migration->get_tables();
|
|
if ( ! is_array( $tables ) ) {
|
|
return array();
|
|
}
|
|
|
|
// for date migration purposes we can assume
|
|
// that all columns need to be the same type
|
|
$info = array();
|
|
foreach( $tables as $t_name => $t_columns ) {
|
|
if ( count( $t_columns ) < 2 ) {
|
|
continue;
|
|
}
|
|
$tbl_error = $this->_check_single_table(
|
|
$t_name,
|
|
$db_migration->get_columns( $t_name ),
|
|
$t_columns
|
|
);
|
|
if ( null !== $tbl_error ) {
|
|
$info[] = $tbl_error;
|
|
}
|
|
}
|
|
return $info;
|
|
}
|
|
|
|
/**
|
|
* Check if single table columns are the same type.
|
|
*
|
|
* @param string $t_name Table name for details purposes.
|
|
* @param array $db_cols Columns from database.
|
|
* @param array $t_columns Columns to check from DDL.
|
|
*
|
|
* @return string|null Inconsistency description, if any.
|
|
*/
|
|
protected function _check_single_table(
|
|
$t_name,
|
|
array $db_cols,
|
|
array $t_columns
|
|
) {
|
|
$type = null;
|
|
foreach ( $db_cols as $c_field => $c_type ) {
|
|
if ( ! in_array( $c_field, $t_columns ) ) {
|
|
continue;
|
|
}
|
|
if ( null === $type ) {
|
|
$type = strtolower( $c_type );
|
|
}
|
|
if ( strtolower( $c_type ) !== $type ) {
|
|
return sprintf(
|
|
Ai1ec_I18n::__(
|
|
'Date columns in table %s have different types.'
|
|
),
|
|
$t_name
|
|
);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get fully qualified table name, to use in queries.
|
|
*
|
|
* @param string $table Name of table, to convert.
|
|
*
|
|
* @return string Qualified table name.
|
|
*/
|
|
protected function _table( $table ) {
|
|
$prefix = $this->_db->get_table_name( 'ai1ec_' );
|
|
if ( substr( $table, 0, strlen( $prefix ) ) !== $prefix ) {
|
|
$table = $prefix . $table;
|
|
}
|
|
return $table;
|
|
}
|
|
|
|
/**
|
|
* _escape_column method
|
|
*
|
|
* Escape column, enquoting it in MySQL specific characters
|
|
*
|
|
* @param string $name Name of column to quote
|
|
*
|
|
* @return string Escaped column name
|
|
*/
|
|
protected function _escape_column( $name ) {
|
|
return '`' . $name . '`';
|
|
}
|
|
|
|
}
|