JFIF $ d d 7 * Z qn )ZP 0 !1 A "#a$2Qr D8 a Ri [f \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H ~ , l s / 9 ć: [=/#f ; ) x { ٛE Q )~ = 𘙲r*2~ a _V = ' ku m F D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq 1dđ #̩ ͑ 0 ; H ] u 搂 @:~ ו K L N s h } O I R* 8 :2 ! lD JV o ( 3 = M ( z Ȱ+i *N A r6K n S l ) ! J J Ӂ* % ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c P A\IL Nj! J ۱+ O\ ͔ ] ޒ S J ȧ c%A N ol ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0 'G=i 2N +0 e 2 ]] 9 VT P O 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P < 0 x < !1AQ "Raq 02Br #S CTb ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV YT D I 6 2 '8Y2 7'\T P.6 d &˦ @V q i |8 -O Ε ] ʔ U= T L 8= ;6c | !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U#6w + +s &r+n ڐ { @ 29gL u " T Ù M=6 (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 ! OS r D 5 7 % | ?x> L | / nD 6?/8 w # [ ) L 7 +6〼T A T g ! %5 Mm Z /c -{1_Je "| ^$ 'O & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@ u6 BԒ i YN * f 0 _ GhKW u lT <nj- 0{ M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q $ H8o Q˼EV ~ k 0 h' L Pp M -M" m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @~p # !1QAa q ? AL0L x LU 4 d ; x " 16ad0 7.\ߋ 0 = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X &Q З non]я u ג./ ; gZ$ M QР 2M ]h ni 0 P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _ 1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3 6 #Ik+ v { v i ( ވ ? a e) X xy/*@Q Ǽ Ծ ) $ IWO l c G; !i k{ gOŀ L ('x T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ) X 5 c QɎMy W ? Q *!R > 6=7)Xj 5`J ] e 8%t ! + ' ! 1Q 5 !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $ v , ,m 㵜5u bV =sY+ @d { N ! dn O < .- B; _w J t 6;Q Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш e黅X ۢ F :)[ F G X ƹ / w _c MeD ,ʡc c . W D tA $ j@ : ) - #u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P 6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L ! ~ y5 g9T̅J kbM' +s :S +B) v @ M j e C f j E 0 Y\Qn z G 1 д~ W o{T 9?` Rm yhsy3 !HAD ] m c1 ~ 2 L S u7x T ;j$ `} 4-> L# vzŏ I L S ֭ T { r jGK C ; b pU= - ` Bs K. S F w4 M q] Z dH S 0 ) tLg
<?php
namespace Yoast\WP\Lib\Migrations;
use Exception;
use Yoast\WP\Lib\Model;
/**
* Yoast migrations adapter class.
*/
class Adapter {
/**
* The version of this adapter.
*
* @var string
*/
private $version = '1.0';
/**
* Whether or not a transaction has been started.
*
* @var bool
*/
private $in_transaction = false;
/**
* Returns the current database name.
*
* @return string
*/
public function get_database_name() {
global $wpdb;
return $wpdb->dbname;
}
/**
* Checks support for migrations.
*
* @return bool
*/
public function supports_migrations() {
return true;
}
/**
* Returns all column native types.
*
* @return array
*/
public function native_database_types() {
$types = [
'primary_key' => [
'name' => 'integer',
'limit' => 11,
'null' => false,
],
'string' => [
'name' => 'varchar',
'limit' => 255,
],
'text' => [ 'name' => 'text' ],
'tinytext' => [ 'name' => 'tinytext' ],
'mediumtext' => [ 'name' => 'mediumtext' ],
'integer' => [
'name' => 'int',
'limit' => 11,
],
'tinyinteger' => [ 'name' => 'tinyint' ],
'smallinteger' => [ 'name' => 'smallint' ],
'mediuminteger' => [ 'name' => 'mediumint' ],
'biginteger' => [ 'name' => 'bigint' ],
'float' => [ 'name' => 'float' ],
'decimal' => [
'name' => 'decimal',
'scale' => 0,
'precision' => 10,
],
'datetime' => [ 'name' => 'datetime' ],
'timestamp' => [ 'name' => 'timestamp' ],
'time' => [ 'name' => 'time' ],
'date' => [ 'name' => 'date' ],
'binary' => [ 'name' => 'blob' ],
'tinybinary' => [ 'name' => 'tinyblob' ],
'mediumbinary' => [ 'name' => 'mediumblob' ],
'longbinary' => [ 'name' => 'longblob' ],
'boolean' => [
'name' => 'tinyint',
'limit' => 1,
],
'enum' => [
'name' => 'enum',
'values' => [],
],
'uuid' => [
'name' => 'char',
'limit' => 36,
],
'char' => [ 'name' => 'char' ],
];
return $types;
}
/**
* Checks if a table exists.
*
* @param string $table The table name.
*
* @return bool
*/
public function has_table( $table ) {
return $this->table_exists( $table );
}
/**
* Allows overriding the hardcoded schema table name constant in case of parallel migrations.
*
* @return string
*/
public function get_schema_version_table_name() {
return Model::get_table_name( 'migrations' );
}
/**
* Create the schema table, if necessary.
*
* @return void
*/
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 ] );
$t->finish();
$this->add_index( $this->get_schema_version_table_name(), 'version', [ 'unique' => true ] );
}
}
/**
* Starts a transaction.
*
* @return void
*/
public function start_transaction() {
if ( $this->in_transaction() === false ) {
$this->begin_transaction();
}
}
/**
* Commits a transaction.
*
* @return void
*/
public function commit_transaction() {
if ( $this->in_transaction() ) {
$this->commit();
}
}
/**
* Rollbacks a transaction.
*
* @return void
*/
public function rollback_transaction() {
if ( $this->in_transaction() ) {
$this->rollback();
}
}
/**
* Quotes a table name string.
*
* @param string $text Table name.
*
* @return string
*/
public function quote_table( $text ) {
return '`' . $text . '`';
}
/**
* 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.
*
* @return string
*/
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.
*
* @return bool
*/
public function database_exists( $database ) {
$ddl = 'SHOW DATABASES';
$result = $this->select_all( $ddl );
if ( \count( $result ) === 0 ) {
return false;
}
foreach ( $result as $dbrow ) {
if ( $dbrow['Database'] === $database ) {
return true;
}
}
return false;
}
/**
* Creates a database.
*
* @param string $db The database name.
*
* @return bool
*/
public function create_database( $db ) {
if ( $this->database_exists( $db ) ) {
return false;
}
$ddl = \sprintf( 'CREATE DATABASE %s', $this->identifier( $db ) );
$result = $this->query( $ddl );
return $result === true;
}
/**
* Drops a database.
*
* @param string $db The database name.
*
* @return bool
*/
public function drop_database( $db ) {
if ( ! $this->database_exists( $db ) ) {
return false;
}
$ddl = \sprintf( 'DROP DATABASE IF EXISTS %s', $this->identifier( $db ) );
$result = $this->query( $ddl );
return $result === true;
}
/**
* Checks if a table exists.
*
* @param string $table The table name.
*
* @return bool
*/
public function table_exists( $table ) {
global $wpdb;
// 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->last_error = '';
$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.
*
* @return bool
*/
public function execute( $query ) {
return $this->query( $query );
}
/**
* Executes a query.
*
* @param string $query The query to run.
*
* @return bool Whether or not the query was performed succesfully.
*/
public function query( $query ) {
global $wpdb;
$query_type = $this->determine_query_type( $query );
$data = [];
if ( $query_type === Constants::SQL_SELECT || $query_type === Constants::SQL_SHOW ) {
$data = $wpdb->get_results( $query, \ARRAY_A );
if ( $data === false ) {
return false;
}
return $data;
}
else {
// INSERT, DELETE, etc...
$result = $wpdb->query( $query );
if ( $result === false ) {
return false;
}
if ( $query_type === Constants::SQL_INSERT ) {
return $wpdb->insert_id;
}
return true;
}
}
/**
* 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 ) {
global $wpdb;
$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 false;
}
return $wpdb->last_result[0];
}
return false;
}
/**
* 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.
*
* @return bool
*/
public function execute_ddl( $ddl ) {
return $this->query( $ddl );
}
/**
* Drops a table
*
* @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 );
}
/**
* Creates a table.
*
* @param string $table_name The table name.
* @param array $options The options.
*
* @return Table
*/
public function create_table( $table_name, $options = [] ) {
return new Table( $this, $table_name, $options );
}
/**
* Escapes a string for usage in queries.
*
* @param string $text The string.
*
* @return string
*/
public function quote_string( $text ) {
global $wpdb;
return $wpdb->_escape( $text );
}
/**
* Returns a quoted string.
*
* @param string $text The string.
*
* @return string
*/
public function identifier( $text ) {
return '`' . $text . '`';
}
/**
* Renames a table.
*
* @param string $name The current table name.
* @param string $new_name The new table name.
*
* @return bool
*/
public function rename_table( $name, $new_name ) {
if ( empty( $name ) || empty( $new_name ) ) {
return false;
}
$sql = \sprintf( 'RENAME TABLE %s TO %s', $this->identifier( $name ), $this->identifier( $new_name ) );
return $this->execute_ddl( $sql );
}
/**
* Adds a column.
*
* @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.
*
* @return bool
*/
public function add_column( $table_name, $column_name, $type, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) || empty( $type ) ) {
return false;
}
// Default types.
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 );
}
/**
* Drops a column.
*
* @param string $table_name The table name.
* @param string $column_name The column name.
*
* @return bool
*/
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 );
}
/**
* Renames a column.
*
* @param string $table_name The table name.
* @param string $column_name The column name.
* @param string $new_column_name The new column name.
*
* @return bool
*/
public function rename_column( $table_name, $column_name, $new_column_name ) {
if ( empty( $table_name ) || empty( $column_name ) || empty( $new_column_name ) ) {
return false;
}
$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 );
}
/**
* Changes a column.
*
* @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.
*
* @return bool
*/
public function change_column( $table_name, $column_name, $type, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) || empty( $type ) ) {
return false;
}
$column_info = $this->column_info( $table_name, $column_name );
// Default types.
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` CHANGE `%s` `%s` %s', $table_name, $column_name, $column_name, $this->type_to_sql( $type, $options ) );
$sql .= $this->add_column_options( $type, $options );
return $this->execute_ddl( $sql );
}
/**
* Returns the database information for a column.
*
* @param string $table The table name.
* @param string $column The column name.
*
* @return array|null
*/
public function column_info( $table, $column ) {
if ( empty( $table ) || empty( $column ) ) {
return null;
}
try {
$sql = \sprintf( "SHOW FULL COLUMNS FROM %s LIKE '%s'", $this->identifier( $table ), $column );
$result = $this->select_one( $sql );
if ( \is_array( $result ) ) {
$result = \array_change_key_case( $result, \CASE_LOWER );
}
return $result;
} catch ( Exception $e ) {
return null;
}
}
/**
* Adds an index.
*
* @param string $table_name The table name.
* @param array|string $column_name The column name(s).
* @param array $options Index options.
*
* @return bool
*/
public function add_index( $table_name, $column_name, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) ) {
return false;
}
// Unique index?
if ( \is_array( $options ) && \array_key_exists( 'unique', $options ) && $options['unique'] === true ) {
$unique = true;
}
else {
$unique = false;
}
// Did the user specify an index name?
if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) {
$index_name = $options['name'];
}
else {
$index_name = $this->get_index_name( $table_name, $column_name );
}
if ( \strlen( $index_name ) > Constants::MYSQL_MAX_IDENTIFIER_LENGTH ) {
return false;
}
if ( ! \is_array( $column_name ) ) {
$column_names = [ $column_name ];
}
else {
$column_names = $column_name;
}
$cols = [];
foreach ( $column_names as $name ) {
$cols[] = $this->identifier( $name );
}
$sql = \sprintf(
'CREATE %sINDEX %s ON %s(%s)',
( $unique === true ) ? 'UNIQUE ' : '',
$this->identifier( $index_name ),
$this->identifier( $table_name ),
\implode( ', ', $cols ),
);
return $this->execute_ddl( $sql );
}
/**
* Drops an index.
*
* @param string $table_name The table name.
* @param array|string $column_name The column name(s).
* @param array $options Index options.
*
* @return bool
*/
public function remove_index( $table_name, $column_name, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) ) {
return false;
}
// Did the user specify an index name?
if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) {
$index_name = $options['name'];
}
else {
$index_name = $this->get_index_name( $table_name, $column_name );
}
$sql = \sprintf( 'DROP INDEX %s ON %s', $this->identifier( $index_name ), $this->identifier( $table_name ) );
return $this->execute_ddl( $sql );
}
/**
* Adds timestamps.
*
* @param string $table_name The table name.
* @param string $created_column_name Created at column name.
* @param string $updated_column_name Updated at column name.
*
* @return bool
*/
public function add_timestamps( $table_name, $created_column_name, $updated_column_name ) {
if ( empty( $table_name ) || empty( $created_column_name ) || empty( $updated_column_name ) ) {
return false;
}
$created_at = $this->add_column( $table_name, $created_column_name, 'datetime' );
$updated_at = $this->add_column(
$table_name,
$updated_column_name,
'timestamp',
[
'null' => false,
'default' => 'CURRENT_TIMESTAMP',
'extra' => 'ON UPDATE CURRENT_TIMESTAMP',
],
);
return $created_at && $updated_at;
}
/**
* Removes timestamps.
*
* @param string $table_name The table name.
* @param string $created_column_name Created at column name.
* @param string $updated_column_name Updated at column name.
*
* @return bool Whether or not the timestamps were removed.
*/
public function remove_timestamps( $table_name, $created_column_name, $updated_column_name ) {
if ( empty( $table_name ) || empty( $created_column_name ) || empty( $updated_column_name ) ) {
return false;
}
$updated_at = $this->remove_column( $table_name, $created_column_name );
$created_at = $this->remove_column( $table_name, $updated_column_name );
return $created_at && $updated_at;
}
/**
* Checks an index.
*
* @param string $table_name The table name.
* @param array|string $column_name The column name(s).
* @param array $options Index options.
*
* @return bool Whether or not the index exists.
*/
public function has_index( $table_name, $column_name, $options = [] ) {
if ( empty( $table_name ) || empty( $column_name ) ) {
return false;
}
// Did the user specify an index name?
if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) {
$index_name = $options['name'];
}
else {
$index_name = $this->get_index_name( $table_name, $column_name );
}
$indexes = $this->indexes( $table_name );
foreach ( $indexes as $idx ) {
if ( $idx['name'] === $index_name ) {
return true;
}
}
return false;
}
/**
* Returns all indexes of a table.
*
* @param string $table_name The table name.
*
* @return array
*/
public function indexes( $table_name ) {
$sql = \sprintf( 'SHOW KEYS FROM %s', $this->identifier( $table_name ) );
$result = $this->select_all( $sql );
$indexes = [];
foreach ( $result as $row ) {
// Skip primary.
if ( $row['Key_name'] === 'PRIMARY' ) {
continue;
}
$indexes[] = [
'name' => $row['Key_name'],
'unique' => (int) $row['Non_unique'] === 0,
];
}
return $indexes;
}
/**
* Converts a type to sql. Default options:
* $limit = null, $precision = null, $scale = null
*
* @param string $type The native type.
* @param array $options The options.
*
* @return string The SQL type.
*
* @throws Exception If invalid arguments are supplied.
*/
public function type_to_sql( $type, $options = [] ) {
$natives = $this->native_database_types();
if ( ! \array_key_exists( $type, $natives ) ) {
$error = \sprintf( "Error:I dont know what column type of '%s' maps to for MySQL.", $type );
$error .= "\nYou provided: {$type}\n";
$error .= "Valid types are: \n";
$types = \array_keys( $natives );
foreach ( $types as $t ) {
if ( $t === 'primary_key' ) {
continue;
}
$error .= "\t{$t}\n";
}
throw new Exception( $error );
}
$scale = null;
$precision = null;
$limit = null;
if ( isset( $options['precision'] ) ) {
$precision = $options['precision'];
}
if ( isset( $options['scale'] ) ) {
$scale = $options['scale'];
}
if ( isset( $options['limit'] ) ) {
$limit = $options['limit'];
}
if ( isset( $options['values'] ) ) {
$values = $options['values'];
}
$native_type = $natives[ $type ];
if ( \is_array( $native_type ) && \array_key_exists( 'name', $native_type ) ) {
$column_type_sql = $native_type['name'];
}
else {
return $native_type;
}
if ( $type === 'decimal' || $type === 'float' ) {
// Ignore limit, use precison and scale.
if ( $precision === null && \array_key_exists( 'precision', $native_type ) ) {
$precision = $native_type['precision'];
}
if ( $scale === null && \array_key_exists( 'scale', $native_type ) ) {
$scale = $native_type['scale'];
}
if ( $precision !== null ) {
if ( \is_int( $scale ) ) {
$column_type_sql .= \sprintf( '(%d, %d)', $precision, $scale );
}
else {
$column_type_sql .= \sprintf( '(%d)', $precision );
}
}
elseif ( $scale ) {
throw new Exception( "Error adding $type column: precision cannot be empty if scale is specified" );
}
}
elseif ( $type === 'enum' ) {
if ( empty( $values ) ) {
throw new Exception( 'Error adding enum column: there must be at least one value defined' );
}
else {
$column_type_sql .= \sprintf(
"('%s')",
\implode( "','", \array_map( [ $this, 'quote_string' ], $values ) ),
);
}
}
// Not a decimal column.
if ( $limit === null && \array_key_exists( 'limit', $native_type ) ) {
$limit = $native_type['limit'];
}
if ( $limit ) {
$column_type_sql .= \sprintf( '(%d)', $limit );
}
return $column_type_sql;
}
/**
* Adds column options.
*
* @param string $type The native type.
* @param array $options The options.
*
* @return string The SQL statement for the column options.
*
* @throws Exception If invalid arguments are supplied.
*/
public function add_column_options( $type, $options ) {
$sql = '';
if ( ! \is_array( $options ) ) {
return $sql;
}
if ( \array_key_exists( 'unsigned', $options ) && $options['unsigned'] === true ) {
$sql .= ' UNSIGNED';
}
if ( \array_key_exists( 'character', $options ) ) {
$sql .= \sprintf( ' CHARACTER SET %s', $this->identifier( $options['character'] ) );
}
if ( \array_key_exists( 'collate', $options ) ) {
$sql .= \sprintf( ' COLLATE %s', $this->identifier( $options['collate'] ) );
}
if ( \array_key_exists( 'auto_increment', $options ) && $options['auto_increment'] === true ) {
$sql .= ' auto_increment';
}
if ( \array_key_exists( 'default', $options ) && $options['default'] !== null ) {
if ( $this->is_sql_method_call( $options['default'] ) ) {
throw new Exception( 'MySQL does not support function calls as default values, constants only.' );
}
if ( \is_int( $options['default'] ) ) {
$default_format = '%d';
}
elseif ( \is_bool( $options['default'] ) ) {
$default_format = "'%d'";
}
elseif ( $options['default'] === 'CURRENT_TIMESTAMP' ) {
$default_format = '%s';
}
else {
$default_format = "'%s'";
}
$default_value = \sprintf( $default_format, $options['default'] );
$sql .= \sprintf( ' DEFAULT %s', $default_value );
}
if ( \array_key_exists( 'null', $options ) ) {
if ( $options['null'] === false || $options['null'] === 'NO' ) {
$sql .= ' NOT NULL';
}
elseif ( $type === 'timestamp' ) {
$sql .= ' NULL';
}
}
if ( \array_key_exists( 'comment', $options ) ) {
$sql .= \sprintf( " COMMENT '%s'", $this->quote_string( $options['comment'] ) );
}
if ( \array_key_exists( 'extra', $options ) ) {
$sql .= \sprintf( ' %s', $this->quote_string( $options['extra'] ) );
}
if ( \array_key_exists( 'after', $options ) ) {
$sql .= \sprintf( ' AFTER %s', $this->identifier( $options['after'] ) );
}
return $sql;
}
/**
* Returns a list of all versions that have been migrated.
*
* @return string[] The version numbers that have been migrated.
*/
public function get_migrated_versions() {
$result = $this->select_all( \sprintf( 'SELECT version FROM %s', $this->get_schema_version_table_name() ) );
return \array_column( $result, 'version' );
}
/**
* Adds a migrated version.
*
* @param string $version The version.
*
* @return bool Whether or not the version was succesfully set.
*/
public function add_version( $version ) {
$sql = \sprintf( "INSERT INTO %s (version) VALUES ('%s')", $this->get_schema_version_table_name(), $version );
return $this->execute_ddl( $sql );
}
/**
* Removes a migrated version.
*
* @param string $version The version.
*
* @return bool Whether or not the version was succesfully removed.
*/
public function remove_version( $version ) {
$sql = \sprintf( "DELETE FROM %s WHERE version = '%s'", $this->get_schema_version_table_name(), $version );
return $this->execute_ddl( $sql );
}
/**
* Returns a message displaying the current version
*
* @return string
*/
public function __toString() {
return self::class . ', version ' . $this->version;
}
/**
* Returns an index name.
*
* @param string $table_name The table name.
* @param string $column_name The column name.
*
* @return string The index name.
*/
private function get_index_name( $table_name, $column_name ) {
$name = \preg_replace( '/\\W/', '_', $table_name );
$name = \preg_replace( '/\\_{2,}/', '_', $name );
// If the column parameter is an array then the user wants to create a multi-column index.
if ( \is_array( $column_name ) ) {
$column_str = \implode( '_and_', $column_name );
}
else {
$column_str = $column_name;
}
$name .= \sprintf( '_%s', $column_str );
return $name;
}
/**
* Returns the type of a query.
*
* @param string $query The query to run.
*
* @return int The query type.
*/
private function determine_query_type( $query ) {
$query = \strtolower( \trim( $query ) );
$match = [];
\preg_match( '/^(\\w)*/i', $query, $match );
$type = $match[0];
switch ( $type ) {
case 'select':
return Constants::SQL_SELECT;
case 'update':
return Constants::SQL_UPDATE;
case 'delete':
return Constants::SQL_DELETE;
case 'insert':
return Constants::SQL_INSERT;
case 'alter':
return Constants::SQL_ALTER;
case 'drop':
return Constants::SQL_DROP;
case 'create':
return Constants::SQL_CREATE;
case 'show':
return Constants::SQL_SHOW;
case 'rename':
return Constants::SQL_RENAME;
case 'set':
return Constants::SQL_SET;
default:
return Constants::SQL_UNKNOWN_QUERY_TYPE;
}
}
/**
* Detect whether or not the string represents a function call and if so
* do not wrap it in single-quotes, otherwise do wrap in single quotes.
*
* @param string $text The string.
*
* @return bool Whether or not it's a SQL function call.
*/
private function is_sql_method_call( $text ) {
$text = \trim( $text );
if ( \substr( $text, -2, 2 ) === '()' ) {
return true;
}
return false;
}
/**
* Checks if a transaction is active.
*
* @return bool
*/
private function in_transaction() {
return $this->in_transaction;
}
/**
* Starts a transaction.
*
* @return void
*
* @throws Exception If a transaction was already started.
*/
private function begin_transaction() {
global $wpdb;
if ( $this->in_transaction === true ) {
throw new Exception( 'Transaction already started' );
}
$wpdb->query( 'START TRANSACTION' );
$this->in_transaction = true;
}
/**
* Commits a transaction.
*
* @return void
*
* @throws Exception If no transaction was strated.
*/
private function commit() {
global $wpdb;
if ( $this->in_transaction === false ) {
throw new Exception( 'Transaction not started' );
}
$wpdb->query( 'COMMIT' );
$this->in_transaction = false;
}
/**
* Rollbacks a transaction.
*
* @return void
*
* @throws Exception If no transaction was started.
*/
private function rollback() {
global $wpdb;
if ( $this->in_transaction === false ) {
throw new Exception( 'Transaction not started' );
}
$wpdb->query( 'ROLLBACK' );
$this->in_transaction = false;
}
}