756 lines
25 KiB
PHP
756 lines
25 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Ai1ec_Database class
|
|
*
|
|
* Class responsible for generic database operations
|
|
*
|
|
* @author Time.ly Network Inc.
|
|
* @since 2.0
|
|
*
|
|
* @package AI1EC
|
|
* @subpackage AI1EC.Database
|
|
*/
|
|
class Ai1ec_Database_Helper {
|
|
|
|
/**
|
|
* @var array Map of tables and their parsed definitions
|
|
*/
|
|
protected $_schema_delta = array();
|
|
|
|
/**
|
|
* @var array List of valid table prefixes
|
|
*/
|
|
protected $_prefixes = array();
|
|
|
|
/**
|
|
* @var wpdb Localized instance of wpdb object
|
|
*/
|
|
protected $_db = NULL;
|
|
|
|
/**
|
|
* @var bool If set to true - no operations will be performed
|
|
*/
|
|
protected $_dry_run = false;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param Ai1ec_Registry_Object $registry
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct( Ai1ec_Registry_Object $registry ) {
|
|
$this->_db = $registry->get( 'dbi.dbi' );
|
|
$this->_prefixes = array(
|
|
$this->_db->get_table_name( 'ai1ec_' ),
|
|
$this->_db->get_table_name(),
|
|
'',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if dry run is enabled
|
|
*
|
|
* @param bool $dry Change dryness [optional=NULL]
|
|
*
|
|
* @return bool Dryness of run or previous value
|
|
*/
|
|
public function is_dry( $dry = NULL ) {
|
|
if ( NULL !== $dry ) {
|
|
$previous = $this->_dry_run;
|
|
$this->_dry_run = (bool)$dry;
|
|
return $previous;
|
|
}
|
|
return $this->_dry_run;
|
|
}
|
|
|
|
/**
|
|
* Get fully-qualified table name given it's abbreviated form
|
|
*
|
|
* @param string $name Name (abbreviation) of table to check
|
|
* @param bool $ignore_check Return longest name if no table exist [false]
|
|
*
|
|
* @return string Fully-qualified table name
|
|
*
|
|
* @throws Ai1ec_Database_Schema_Exception If no table matches
|
|
*/
|
|
public function table( $name, $ignore_check = false ) {
|
|
$existing = $this->get_all_tables();
|
|
$table = NULL;
|
|
$candidate = NULL;
|
|
foreach ( $this->_prefixes as $prefix ) {
|
|
$candidate = $prefix . $name;
|
|
$index = strtolower( $candidate );
|
|
if ( isset( $existing[$index] ) ) {
|
|
$table = $existing[$index];
|
|
break;
|
|
}
|
|
}
|
|
if ( NULL === $table ) {
|
|
if ( true === $ignore_check ) {
|
|
return $candidate;
|
|
}
|
|
throw new Ai1ec_Database_Schema_Exception(
|
|
'Table \'' . $name . '\' does not exist'
|
|
);
|
|
}
|
|
return $table;
|
|
}
|
|
|
|
/**
|
|
* Drop given indices from table
|
|
*
|
|
* @param string $table Name of table to modify
|
|
* @param string|array $indices List, or single, of indices to remove
|
|
*
|
|
* @return bool Success
|
|
*
|
|
* @throws Ai1ec_Database_Schema_Exception If table is not found
|
|
*/
|
|
public function drop_indices( $table, $indices ) {
|
|
if ( ! is_array( $indices ) ) {
|
|
$indices = array( (string)$indices );
|
|
}
|
|
$table = $this->table( $table );
|
|
$existing = $this->get_indices( $table );
|
|
$removed = 0;
|
|
foreach ( $indices as $index ) {
|
|
if (
|
|
! isset( $existing[$index] ) ||
|
|
$this->_dry_query(
|
|
'ALTER TABLE ' . $table . ' DROP INDEX ' . $index
|
|
)
|
|
) {
|
|
++$removed;
|
|
}
|
|
}
|
|
return ( count( $indices ) === $removed );
|
|
}
|
|
|
|
/**
|
|
* Create indices for given table
|
|
*
|
|
* Input ({@see $indices}) must be the same, as output of
|
|
* method {@see self::get_indices()}.
|
|
*
|
|
* @param string $table Name of table to create indices for
|
|
* @param array $indices Indices representation to be created
|
|
*
|
|
* @return bool Success
|
|
*
|
|
* @throws Ai1ec_Database_Schema_Exception If table is not found
|
|
*/
|
|
public function create_indices( $table, array $indices ) {
|
|
$table = $this->table( $table );
|
|
foreach ( $indices as $name => $definition ) {
|
|
$query = 'ALTER TABLE ' . $table . ' ADD ';
|
|
if ( $definition['unique'] ) {
|
|
$query .= 'UNIQUE ';
|
|
}
|
|
$query .= 'KEY ' . $name . ' (' .
|
|
implode( ', ', $definition['columns'] ) .
|
|
')';
|
|
if ( ! $this->_dry_query( $query ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* get_indices method
|
|
*
|
|
* Get map of indices defined for table.
|
|
*
|
|
* @NOTICE: no optimization will be performed here, and response will not
|
|
* be cached, to allow checking result of DDL statements.
|
|
*
|
|
* Returned array structure (example):
|
|
* array(
|
|
* 'index_name' => array(
|
|
* 'name' => 'index_name',
|
|
* 'columns' => array(
|
|
* 'column1',
|
|
* 'column2',
|
|
* 'column3',
|
|
* ),
|
|
* 'unique' => true,
|
|
* ),
|
|
* )
|
|
*
|
|
* @param string $table Name of table to retrieve index names for
|
|
*
|
|
* @return array Map of index names and their representation
|
|
*
|
|
* @throws Ai1ec_Database_Schema_Exception If table is not found
|
|
*/
|
|
public function get_indices( $table ) {
|
|
$sql_query = 'SHOW INDEXES FROM ' . $this->table( $table );
|
|
$result = $this->_db->get_results( $sql_query );
|
|
$indices = array();
|
|
foreach ( $result as $index ) {
|
|
$name = $index->Key_name;
|
|
if ( ! isset( $indices[$name] ) ) {
|
|
$indices[$name] = array(
|
|
'name' => $name,
|
|
'columns' => array(),
|
|
'unique' => ! (bool)intval( $index->Non_unique ),
|
|
);
|
|
}
|
|
$indices[$name]['columns'][$index->Column_name] = $index->Sub_part;
|
|
}
|
|
return $indices;
|
|
}
|
|
|
|
/**
|
|
* Perform query, unless `dry_run` is selected. In later case just output
|
|
* the final query and return true.
|
|
*
|
|
* @param string $query SQL Query to execute
|
|
*
|
|
* @return mixed Query state, or true in dry run mode
|
|
*/
|
|
public function _dry_query( $query ) {
|
|
if ( $this->is_dry() ) {
|
|
pr( $query );
|
|
return true;
|
|
}
|
|
$result = $this->_db->query( $query );
|
|
if ( AI1EC_DEBUG ) {
|
|
echo '<h4>', $query, '</h4><pre>', var_export( $result, true ), '</pre>';
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Check if given table exists
|
|
*
|
|
* @param string $table Name of table to check
|
|
*
|
|
* @return bool Existance
|
|
*/
|
|
public function table_exists( $table ) {
|
|
$map = $this->get_all_tables();
|
|
return isset( $map[strtolower( $table )] );
|
|
}
|
|
|
|
/**
|
|
* Return a list of all tables currently present
|
|
*
|
|
* @return array Map of tables present
|
|
*/
|
|
public function get_all_tables() {
|
|
/**
|
|
* @TODO: refactor using dbi.dbi::get_tables
|
|
*/
|
|
$sql_query = 'SHOW TABLES LIKE \'' .
|
|
$this->_db->get_table_name() .
|
|
'%\'';
|
|
$result = $this->_db->get_col( $sql_query );
|
|
$tables = array();
|
|
foreach ( $result as $table ) {
|
|
$tables[strtolower( $table )] = $table;
|
|
}
|
|
return $tables;
|
|
}
|
|
|
|
/**
|
|
* apply_delta method
|
|
*
|
|
* Attempt to parse and apply given database tables definition, as a delta.
|
|
* Some validation is made prior to calling DB, and fields/indexes are also
|
|
* checked for consistency after sending queries to DB.
|
|
*
|
|
* NOTICE: only "CREATE TABLE" statements are handled. Others will, likely,
|
|
* be ignored, if passed through this method.
|
|
*
|
|
* @param string|array $query Single or multiple queries to perform on DB
|
|
*
|
|
* @return bool Success
|
|
*
|
|
* @throws Ai1ec_Database_Error In case of any error
|
|
*/
|
|
public function apply_delta( $query ) {
|
|
if ( ! function_exists( 'dbDelta' ) ) {
|
|
require_once ABSPATH . 'wp-admin' . DIRECTORY_SEPARATOR .
|
|
'includes' . DIRECTORY_SEPARATOR . 'upgrade.php';
|
|
}
|
|
$success = false;
|
|
$this->_schema_delta = array();
|
|
$queries = $this->_prepare_delta( $query );
|
|
$result = dbDelta( $queries );
|
|
$success = $this->_check_delta();
|
|
return $success;
|
|
}
|
|
|
|
/**
|
|
* get_notices_helper method
|
|
*
|
|
* DIP implementing method, to give access to Ai1ec_Deferred_Rendering_Helper.
|
|
*
|
|
* @param Ai1ec_Deferred_Rendering_Helper $replacement Notices implementor
|
|
*
|
|
* @return Ai1ec_Deferred_Rendering_Helper Instance of notices implementor
|
|
*/
|
|
public function get_notices_helper(
|
|
Ai1ec_Deferred_Rendering_Helper $replacement = NULL
|
|
) {
|
|
static $helper = NULL;
|
|
if ( NULL !== $replacement ) {
|
|
$helper = $replacement;
|
|
}
|
|
if ( NULL === $helper ) {
|
|
$helper = Ai1ec_Deferred_Rendering_Helper::get_instance();
|
|
}
|
|
return $helper;
|
|
}
|
|
|
|
/**
|
|
* _prepare_delta method
|
|
*
|
|
* Prepare statements for execution.
|
|
* Attempt to parse various SQL definitions and compose the one, that is
|
|
* most likely to be accepted by delta engine.
|
|
*
|
|
* @param string|array $queries Single or multiple queries to perform on DB
|
|
*
|
|
* @return bool Success
|
|
*
|
|
* @throws Ai1ec_Database_Error In case of any error
|
|
*/
|
|
protected function _prepare_delta( $queries ) {
|
|
if ( ! is_array( $queries ) ) {
|
|
$queries = explode( ';', $queries );
|
|
$queries = array_filter( $queries );
|
|
}
|
|
$current_table = NULL;
|
|
$ctable_regexp = '#
|
|
\s*CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?`?([^ ]+)`?\s*
|
|
\((.+)\)
|
|
([^()]*)
|
|
#six';
|
|
foreach ( $queries as $query ) {
|
|
if ( preg_match( $ctable_regexp, $query, $matches ) ) {
|
|
$this->_schema_delta[$matches[1]] = array(
|
|
'tblname' => $matches[1],
|
|
'cryptic' => NULL,
|
|
'creator' => '',
|
|
'columns' => array(),
|
|
'indexes' => array(),
|
|
'content' => preg_replace( '#`#', '', $matches[2] ),
|
|
'clauses' => $matches[3],
|
|
);
|
|
}
|
|
}
|
|
$this->_parse_delta();
|
|
$sane_queries = array();
|
|
foreach ( $this->_schema_delta as $table => $definition ) {
|
|
$create = 'CREATE TABLE ' . $table . " (\n";
|
|
foreach ( $definition['columns'] as $column ) {
|
|
$create .= ' ' . $column['create'] . ",\n";
|
|
}
|
|
foreach ( $definition['indexes'] as $index ) {
|
|
$create .= ' ' . $index['create'] . ",\n";
|
|
}
|
|
$create = substr( $create, 0, -2 ) . "\n";
|
|
$create .= ')' . $definition['clauses'];
|
|
$this->_schema_delta[$table]['creator'] = $create;
|
|
$this->_schema_delta[$table]['cryptic'] = md5( $create );
|
|
$sane_queries[] = $create;
|
|
}
|
|
return $sane_queries;
|
|
}
|
|
|
|
/**
|
|
* _parse_delta method
|
|
*
|
|
* Parse table application (creation) statements into atomical particles.
|
|
* Here "atomical particles" stands for either columns, or indexes.
|
|
*
|
|
* @return void Method does not return
|
|
*
|
|
* @throws Ai1ec_Database_Error In case of any error
|
|
*/
|
|
protected function _parse_delta() {
|
|
foreach ( $this->_schema_delta as $table => $definitions ) {
|
|
$listing = explode( "\n", $definitions['content'] );
|
|
$listing = array_filter( $listing, array( $this, '_is_not_empty_line' ) );
|
|
$lines = count( $listing );
|
|
$lineno = 0;
|
|
foreach ( $listing as $line ) {
|
|
++$lineno;
|
|
$line = trim( preg_replace( '#\s+#', ' ', $line ) );
|
|
$line_new = rtrim( $line, ',' );
|
|
if (
|
|
$lineno < $lines && $line === $line_new ||
|
|
$lineno == $lines && $line !== $line_new
|
|
) {
|
|
throw new Ai1ec_Database_Error(
|
|
'Missing comma in line \'' . $line . '\''
|
|
);
|
|
}
|
|
$line = $line_new;
|
|
unset( $line_new );
|
|
$type = 'indexes';
|
|
if ( false === ( $record = $this->_parse_index( $line ) ) ) {
|
|
$type = 'columns';
|
|
$record = $this->_parse_column( $line );
|
|
}
|
|
if ( isset(
|
|
$this->_schema_delta[$table][$type][$record['name']]
|
|
) ) {
|
|
throw new Ai1ec_Database_Error(
|
|
'For table `' . $table . '` entry ' . $type .
|
|
' named `' . $record['name'] . '` was declared twice' .
|
|
' in ' . $definitions
|
|
);
|
|
}
|
|
$this->_schema_delta[$table][$type][$record['name']] = $record;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* _parse_index method
|
|
*
|
|
* Given string attempts to detect, if it is an index, and if yes - parse
|
|
* it to more navigable index definition for future validations.
|
|
* Creates modified index create line, for delta application.
|
|
*
|
|
* @param string $description Single "line" of CREATE TABLE statement body
|
|
*
|
|
* @return array|bool Index definition, or false if input does not look like index
|
|
*
|
|
* @throws Ai1ec_Database_Error In case of any error
|
|
*/
|
|
protected function _parse_index( $description ) {
|
|
$description = preg_replace(
|
|
'#^CONSTRAINT(\s+`?[^ ]+`?)?\s+#six',
|
|
'',
|
|
$description
|
|
);
|
|
$details = explode( ' ', $description );
|
|
$index = array(
|
|
'name' => NULL,
|
|
'content' => array(),
|
|
'create' => '',
|
|
);
|
|
$details[0] = strtoupper( $details[0] );
|
|
switch ( $details[0] ) {
|
|
case 'PRIMARY':
|
|
$index['name'] = 'PRIMARY';
|
|
$index['create'] = 'PRIMARY KEY ';
|
|
break;
|
|
|
|
case 'UNIQUE':
|
|
$name = $details[1];
|
|
if (
|
|
0 === strcasecmp( 'KEY', $name ) ||
|
|
0 === strcasecmp( 'INDEX', $name )
|
|
) {
|
|
$name = $details[2];
|
|
}
|
|
$index['name'] = $name;
|
|
$index['create'] = 'UNIQUE KEY ' . $name;
|
|
break;
|
|
|
|
case 'KEY':
|
|
case 'INDEX':
|
|
$index['name'] = $details[1];
|
|
$index['create'] = 'KEY ' . $index['name'];
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
$index['content'] = $this->_parse_index_content( $description );
|
|
$index['create'] .= ' (';
|
|
foreach ( $index['content'] as $column => $length ) {
|
|
$index['create'] .= $column;
|
|
if ( NULL !== $length ) {
|
|
$index['create'] .= '(' . $length . ')';
|
|
}
|
|
$index['create'] .= ',';
|
|
}
|
|
$index['create'] = substr( $index['create'], 0, -1 );
|
|
$index['create'] .= ')';
|
|
return $index;
|
|
}
|
|
|
|
/**
|
|
* _parse_column method
|
|
*
|
|
* Parse column to parseable definition.
|
|
* Some valid definitions may still be not recognizes (namely SET and ENUM)
|
|
* thus one shall beware, when attempting to create such.
|
|
* Create alternative create table entry line for delta application.
|
|
*
|
|
* @param string $description Single "line" of CREATE TABLE statement body
|
|
*
|
|
* @return array Column definition
|
|
*
|
|
* @throws Ai1ec_Database_Error In case of any error
|
|
*/
|
|
protected function _parse_column( $description ) {
|
|
$column_regexp = '#^
|
|
([a-z][a-z_]+)\s+
|
|
(
|
|
[A-Z]+
|
|
(?:\s*\(\s*\d+(?:\s*,\s*\d+\s*)?\s*\))?
|
|
(?:\s+UNSIGNED)?
|
|
(?:\s+ZEROFILL)?
|
|
(?:\s+BINARY)?
|
|
(?:
|
|
\s+CHARACTER\s+SET\s+[a-z][a-z_]+
|
|
(?:\s+COLLATE\s+[a-z][a-z0-9_]+)?
|
|
)?
|
|
)
|
|
(
|
|
\s+(?:NOT\s+)?NULL
|
|
)?
|
|
(
|
|
\s+DEFAULT\s+[^\s]+
|
|
)?
|
|
(\s+ON\s+UPDATE\s+CURRENT_(?:TIMESTAMP|DATE))?
|
|
(\s+AUTO_INCREMENT)?
|
|
\s*,?\s*
|
|
$#six';
|
|
if ( ! preg_match( $column_regexp, $description, $matches ) ) {
|
|
throw new Ai1ec_Database_Error(
|
|
'Invalid column description ' . $description
|
|
);
|
|
}
|
|
$column = array(
|
|
'name' => $matches[1],
|
|
'content' => array(),
|
|
'create' => '',
|
|
);
|
|
if ( 0 === strcasecmp( 'boolean', $matches[2] ) ) {
|
|
$matches[2] = 'tinyint(1)';
|
|
}
|
|
$column['content']['type'] = $matches[2];
|
|
$column['content']['null'] = (
|
|
! isset( $matches[3] ) ||
|
|
0 !== strcasecmp( 'NOT NULL', trim( $matches[3] ) )
|
|
);
|
|
$column['create'] = $column['name'] . ' ' . $column['content']['type'];
|
|
if ( isset( $matches[3] ) ) {
|
|
$column['create'] .= ' ' .
|
|
implode(
|
|
' ',
|
|
array_map(
|
|
'trim',
|
|
array_slice( $matches, 3 )
|
|
)
|
|
);
|
|
}
|
|
return $column;
|
|
}
|
|
|
|
/**
|
|
* _parse_index_content method
|
|
*
|
|
* Parse index content, to a map of columns and their length.
|
|
* All index (content) cases shall be covered, although it is only tested.
|
|
*
|
|
* @param string Single line of CREATE TABLE statement, containing index definition
|
|
*
|
|
* @return array Map of columns and their length, as per index definition
|
|
*
|
|
* @throws Ai1ec_Database_Error In case of any error
|
|
*/
|
|
protected function _parse_index_content( $description ) {
|
|
if ( ! preg_match( '#^[^(]+\((.+)\)$#', $description, $matches ) ) {
|
|
throw new Ai1ec_Database_Error(
|
|
'Invalid index description ' . $description
|
|
);
|
|
}
|
|
$columns = array();
|
|
$textual = explode( ',', $matches[1] );
|
|
$column_regexp = '#\s*([^(]+)(?:\s*\(\s*(\d+)\s*\))?\s*#sx';
|
|
foreach ( $textual as $column ) {
|
|
if (
|
|
! preg_match( $column_regexp, $column, $matches ) || (
|
|
isset( $matches[2] ) &&
|
|
(string)$matches[2] !== (string)intval( $matches[2] )
|
|
)
|
|
) {
|
|
throw new Ai1ec_Database_Error(
|
|
'Invalid index (columns) description ' . $description .
|
|
' as per \'' . $column . '\''
|
|
);
|
|
}
|
|
$matches[1] = trim( $matches[1] );
|
|
$columns[$matches[1]] = NULL;
|
|
if ( isset( $matches[2] ) ) {
|
|
$columns[$matches[1]] = (int)$matches[2];
|
|
}
|
|
}
|
|
return $columns;
|
|
}
|
|
|
|
/**
|
|
* _check_delta method
|
|
*
|
|
* Given parsed schema definitions (in {@see self::$_schema_delta} map) this
|
|
* method performs checks, to ensure that table exists, columns are of
|
|
* expected type, and indexes match their definition in original query.
|
|
*
|
|
* @return bool Success
|
|
*
|
|
* @throws Ai1ec_Database_Error In case of any error
|
|
*/
|
|
protected function _check_delta() {
|
|
if ( empty( $this->_schema_delta ) ) {
|
|
return true;
|
|
}
|
|
foreach ( $this->_schema_delta as $table => $description ) {
|
|
|
|
$columns = $this->_db->get_results( 'SHOW FULL COLUMNS FROM ' . $table );
|
|
if ( empty( $columns ) ) {
|
|
throw new Ai1ec_Database_Error(
|
|
'Required table `' . $table . '` was not created'
|
|
);
|
|
}
|
|
$db_column_names = array();
|
|
foreach ( $columns as $column ) {
|
|
if ( ! isset( $description['columns'][$column->Field] ) ) {
|
|
if ( $this->_db->query(
|
|
'ALTER TABLE `' . $table .
|
|
'` DROP COLUMN `' . $column->Field . '`'
|
|
) ) {
|
|
continue;
|
|
}
|
|
continue; // ignore so far
|
|
//throw new Ai1ec_Database_Error(
|
|
// 'Unknown column `' . $column->Field .
|
|
// '` is present in table `' . $table . '`'
|
|
//);
|
|
}
|
|
$db_column_names[$column->Field] = $column->Field;
|
|
$type_db = $column->Type;
|
|
$collation = '';
|
|
if ( $column->Collation ) {
|
|
$collation = ' CHARACTER SET ' .
|
|
substr(
|
|
$column->Collation,
|
|
0,
|
|
strpos( $column->Collation, '_' )
|
|
) . ' COLLATE ' . $column->Collation;
|
|
}
|
|
$type_req = $description['columns'][$column->Field]
|
|
['content']['type'];
|
|
if (
|
|
false !== stripos(
|
|
$type_req,
|
|
' COLLATE '
|
|
)
|
|
) {
|
|
// suspend collation checking
|
|
//$type_db .= $collation;
|
|
$type_req = preg_replace(
|
|
'#^
|
|
(.+)
|
|
\s+CHARACTER\s+SET\s+[a-z0-9_]+
|
|
\s+COLLATE\s+[a-z0-9_]+
|
|
(.+)?\s*
|
|
$#six',
|
|
'$1$2',
|
|
$type_req
|
|
);
|
|
}
|
|
$type_db = strtolower(
|
|
preg_replace( '#\s+#', '', $type_db )
|
|
);
|
|
$type_req = strtolower(
|
|
preg_replace( '#\s+#', '', $type_req )
|
|
);
|
|
if ( 0 !== strcmp( $type_db, $type_req ) ) {
|
|
throw new Ai1ec_Database_Error(
|
|
'Field `' . $table . '`.`' . $column->Field .
|
|
'` is of incompatible type'
|
|
);
|
|
}
|
|
if (
|
|
'YES' === $column->Null &&
|
|
false === $description['columns'][$column->Field]
|
|
['content']['null'] ||
|
|
'NO' === $column->Null &&
|
|
true === $description['columns'][$column->Field]
|
|
['content']['null']
|
|
) {
|
|
throw new Ai1ec_Database_Error(
|
|
'Field `' . $table . '`.`' . $column->Field .
|
|
'` NULLability is flipped'
|
|
);
|
|
}
|
|
}
|
|
if (
|
|
$missing = array_diff(
|
|
array_keys( $description['columns'] ),
|
|
$db_column_names
|
|
)
|
|
) {
|
|
throw new Ai1ec_Database_Error(
|
|
'In table `' . $table . '` fields are missing: ' .
|
|
implode( ', ', $missing )
|
|
);
|
|
}
|
|
|
|
$indexes = $this->get_indices( $table );
|
|
|
|
foreach ( $indexes as $name => $definition ) {
|
|
if ( ! isset( $description['indexes'][$name] ) ) {
|
|
continue; // ignore so far
|
|
//throw new Ai1ec_Database_Error(
|
|
// 'Unknown index `' . $name .
|
|
// '` is defined for table `' . $table . '`'
|
|
//);
|
|
}
|
|
if (
|
|
$missed = array_diff_assoc(
|
|
$description['indexes'][$name]['content'],
|
|
$definition['columns']
|
|
)
|
|
) {
|
|
throw new Ai1ec_Database_Error(
|
|
'Index `' . $name .
|
|
'` definition for table `' . $table . '` has invalid ' .
|
|
' fields: ' . implode( ', ', array_keys( $missed ) )
|
|
);
|
|
}
|
|
}
|
|
|
|
if (
|
|
$missing = array_diff(
|
|
array_keys( $description['indexes'] ),
|
|
array_keys( $indexes )
|
|
)
|
|
) {
|
|
throw new Ai1ec_Database_Error(
|
|
'In table `' . $table . '` indexes are missing: ' .
|
|
implode( ', ', $missing )
|
|
);
|
|
}
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* _is_not_empty_line method
|
|
*
|
|
* Helper method, to check that any given line is not empty.
|
|
* Aids array_filter in detecting empty SQL query lines.
|
|
*
|
|
* @param string $line Single line of DB query statement
|
|
*
|
|
* @return bool True if line is not empty, false otherwise
|
|
*/
|
|
protected function _is_not_empty_line( $line ) {
|
|
$line = trim( $line );
|
|
return ! empty( $line );
|
|
}
|
|
|
|
}
|