PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.1
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.1
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / lib / migrations / adapter.php

adapter.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.1, at lib/migrations/adapter.php

1,073 lines 26.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Yoast\WP\Lib\Migrations;
4
5 use Exception;
6 use Yoast\WP\Lib\Model;
7
8 /**
9 * Yoast migrations adapter class.
10 */
11 class Adapter {
12
13 /**
14 * The version of this adapter.
15 *
16 * @var string
17 */
18 private $version = '1.0';
19
20 /**
21 * Whether or not a transaction has been started.
22 *
23 * @var bool
24 */
25 private $in_transaction = false;
26
27 /**
28 * Returns the current database name.
29 *
30 * @return string
31 */
32 public function get_database_name() {
33 global $wpdb;
34
35 return $wpdb->dbname;
36 }
37
38 /**
39 * Checks support for migrations.
40 *
41 * @return bool
42 */
43 public function supports_migrations() {
44 return true;
45 }
46
47 /**
48 * Returns all column native types.
49 *
50 * @return array
51 */
52 public function native_database_types() {
53 $types = [
54 'primary_key' => [
55 'name' => 'integer',
56 'limit' => 11,
57 'null' => false,
58 ],
59 'string' => [
60 'name' => 'varchar',
61 'limit' => 255,
62 ],
63 'text' => [ 'name' => 'text' ],
64 'tinytext' => [ 'name' => 'tinytext' ],
65 'mediumtext' => [ 'name' => 'mediumtext' ],
66 'integer' => [
67 'name' => 'int',
68 'limit' => 11,
69 ],
70 'tinyinteger' => [ 'name' => 'tinyint' ],
71 'smallinteger' => [ 'name' => 'smallint' ],
72 'mediuminteger' => [ 'name' => 'mediumint' ],
73 'biginteger' => [ 'name' => 'bigint' ],
74 'float' => [ 'name' => 'float' ],
75 'decimal' => [
76 'name' => 'decimal',
77 'scale' => 0,
78 'precision' => 10,
79 ],
80 'datetime' => [ 'name' => 'datetime' ],
81 'timestamp' => [ 'name' => 'timestamp' ],
82 'time' => [ 'name' => 'time' ],
83 'date' => [ 'name' => 'date' ],
84 'binary' => [ 'name' => 'blob' ],
85 'tinybinary' => [ 'name' => 'tinyblob' ],
86 'mediumbinary' => [ 'name' => 'mediumblob' ],
87 'longbinary' => [ 'name' => 'longblob' ],
88 'boolean' => [
89 'name' => 'tinyint',
90 'limit' => 1,
91 ],
92 'enum' => [
93 'name' => 'enum',
94 'values' => [],
95 ],
96 'uuid' => [
97 'name' => 'char',
98 'limit' => 36,
99 ],
100 'char' => [ 'name' => 'char' ],
101 ];
102
103 return $types;
104 }
105
106 /**
107 * Checks if a table exists.
108 *
109 * @param string $table The table name.
110 *
111 * @return bool
112 */
113 public function has_table( $table ) {
114 return $this->table_exists( $table );
115 }
116
117 /**
118 * Allows overriding the hardcoded schema table name constant in case of parallel migrations.
119 *
120 * @return string
121 */
122 public function get_schema_version_table_name() {
123 return Model::get_table_name( 'migrations' );
124 }
125
126 /**
127 * Create the schema table, if necessary.
128 */
129 public function create_schema_version_table() {
130 if ( ! $this->has_table( $this->get_schema_version_table_name() ) ) {
131 $t = $this->create_table( $this->get_schema_version_table_name() );
132 $t->column( 'version', 'string', [ 'limit' => 191 ] );
133 $t->finish();
134 $this->add_index( $this->get_schema_version_table_name(), 'version', [ 'unique' => true ] );
135 }
136 }
137
138 /**
139 * Starts a transaction.
140 */
141 public function start_transaction() {
142 if ( $this->in_transaction() === false ) {
143 $this->begin_transaction();
144 }
145 }
146
147 /**
148 * Commits a transaction.
149 */
150 public function commit_transaction() {
151 if ( $this->in_transaction() ) {
152 $this->commit();
153 }
154 }
155
156 /**
157 * Rollbacks a transaction.
158 */
159 public function rollback_transaction() {
160 if ( $this->in_transaction() ) {
161 $this->rollback();
162 }
163 }
164
165 /**
166 * Quotes a table name string.
167 *
168 * @param string $text Table name.
169 *
170 * @return string
171 */
172 public function quote_table( $text ) {
173 return '`' . $text . '`';
174 }
175
176 /**
177 * Return the SQL definition of a column.
178 *
179 * @param string $column_name The column name.
180 * @param string $type The type of the column.
181 * @param array|null $options Column options.
182 *
183 * @return string
184 */
185 public function column_definition( $column_name, $type, $options = null ) {
186 $col = new Column( $this, $column_name, $type, $options );
187
188 return $col->__toString();
189 }
190
191 /**
192 * Checks if a database exists.
193 *
194 * @param string $database The database name.
195 *
196 * @return bool
197 */
198 public function database_exists( $database ) {
199 $ddl = 'SHOW DATABASES';
200 $result = $this->select_all( $ddl );
201 if ( \count( $result ) === 0 ) {
202 return false;
203 }
204 foreach ( $result as $dbrow ) {
205 if ( $dbrow['Database'] === $database ) {
206 return true;
207 }
208 }
209
210 return false;
211 }
212
213 /**
214 * Creates a database.
215 *
216 * @param string $db The database name.
217 *
218 * @return bool
219 */
220 public function create_database( $db ) {
221 if ( $this->database_exists( $db ) ) {
222 return false;
223 }
224 $ddl = \sprintf( 'CREATE DATABASE %s', $this->identifier( $db ) );
225 $result = $this->query( $ddl );
226
227 return $result === true;
228 }
229
230 /**
231 * Drops a database.
232 *
233 * @param string $db The database name.
234 *
235 * @return bool
236 */
237 public function drop_database( $db ) {
238 if ( ! $this->database_exists( $db ) ) {
239 return false;
240 }
241 $ddl = \sprintf( 'DROP DATABASE IF EXISTS %s', $this->identifier( $db ) );
242 $result = $this->query( $ddl );
243
244 return $result === true;
245 }
246
247 /**
248 * Checks if a table exists.
249 *
250 * @param string $table The table name.
251 *
252 * @return bool
253 */
254 public function table_exists( $table ) {
255 global $wpdb;
256
257 // We need last error to be clear so we can check against it easily.
258 $previous_last_error = $wpdb->last_error;
259 $previous_suppress_errors = $wpdb->suppress_errors;
260 $wpdb->last_error = '';
261 $wpdb->suppress_errors = true;
262
263 $result = $wpdb->query( "SELECT * FROM $table LIMIT 1" );
264
265 // Restore the last error, as this is not truly an error and we don't want to alarm people.
266 $wpdb->last_error = $previous_last_error;
267 $wpdb->suppress_errors = $previous_suppress_errors;
268
269 return $result !== false;
270 }
271
272 /**
273 * Wrapper to execute a query.
274 *
275 * @param string $query The query to run.
276 *
277 * @return bool
278 */
279 public function execute( $query ) {
280 return $this->query( $query );
281 }
282
283 /**
284 * Executes a query.
285 *
286 * @param string $query The query to run.
287 *
288 * @return bool Whether or not the query was performed succesfully.
289 */
290 public function query( $query ) {
291 global $wpdb;
292
293 $query_type = $this->determine_query_type( $query );
294 $data = [];
295 if ( $query_type === Constants::SQL_SELECT || $query_type === Constants::SQL_SHOW ) {
296 $data = $wpdb->get_results( $query, ARRAY_A );
297 if ( $data === false ) {
298 return false;
299 }
300
301 return $data;
302 }
303 else {
304 // INSERT, DELETE, etc...
305 $result = $wpdb->query( $query );
306 if ( $result === false ) {
307 return false;
308 }
309 if ( $query_type === Constants::SQL_INSERT ) {
310 return $wpdb->insert_id;
311 }
312
313 return true;
314 }
315 }
316
317 /**
318 * Returns a single result for a query.
319 *
320 * @param string $query The query to run.
321 *
322 * @return array|false An associative array of the result.
323 */
324 public function select_one( $query ) {
325 global $wpdb;
326
327 $query_type = $this->determine_query_type( $query );
328 if ( $query_type === Constants::SQL_SELECT || $query_type === Constants::SQL_SHOW ) {
329 $result = $wpdb->query( $query );
330 if ( $result === false ) {
331 return false;
332 }
333
334 return $wpdb->last_result[0];
335 }
336
337 return false;
338 }
339
340 /**
341 * Returns all results for a query.
342 *
343 * @param string $query The query to run.
344 *
345 * @return array An array of associative arrays.
346 */
347 public function select_all( $query ) {
348 return $this->query( $query );
349 }
350
351 /**
352 * Use this method for non-SELECT queries.
353 * Or anything where you dont necessarily expect a result string, e.g. DROPs, CREATEs, etc.
354 *
355 * @param string $ddl The query to run.
356 *
357 * @return bool
358 */
359 public function execute_ddl( $ddl ) {
360 return $this->query( $ddl );
361 }
362
363 /**
364 * Drops a table
365 *
366 * @param string $table The table name.
367 *
368 * @return bool Whether or not the table was succesfully dropped.
369 */
370 public function drop_table( $table ) {
371 $ddl = \sprintf( 'DROP TABLE IF EXISTS %s', $this->identifier( $table ) );
372 return $this->query( $ddl );
373 }
374
375 /**
376 * Creates a table.
377 *
378 * @param string $table_name The table name.
379 * @param array $options The options.
380 *
381 * @return Table
382 */
383 public function create_table( $table_name, $options = [] ) {
384 return new Table( $this, $table_name, $options );
385 }
386
387 /**
388 * Escapes a string for usage in queries.
389 *
390 * @param string $text The string.
391 *
392 * @return string
393 */
394 public function quote_string( $text ) {
395 global $wpdb;
396
397 return $wpdb->_escape( $text );
398 }
399
400 /**
401 * Returns a quoted string.
402 *
403 * @param string $text The string.
404 *
405 * @return string
406 */
407 public function identifier( $text ) {
408 return '`' . $text . '`';
409 }
410
411 /**
412 * Renames a table.
413 *
414 * @param string $name The current table name.
415 * @param string $new_name The new table name.
416 *
417 * @return bool
418 */
419 public function rename_table( $name, $new_name ) {
420 if ( empty( $name ) || empty( $new_name ) ) {
421 return false;
422 }
423 $sql = \sprintf( 'RENAME TABLE %s TO %s', $this->identifier( $name ), $this->identifier( $new_name ) );
424
425 return $this->execute_ddl( $sql );
426 }
427
428 /**
429 * Adds a column.
430 *
431 * @param string $table_name The table name.
432 * @param string $column_name The column name.
433 * @param string $type The column type.
434 * @param array $options Column options.
435 *
436 * @return bool
437 */
438 public function add_column( $table_name, $column_name, $type, $options = [] ) {
439 if ( empty( $table_name ) || empty( $column_name ) || empty( $type ) ) {
440 return false;
441 }
442 // Default types.
443 if ( ! \array_key_exists( 'limit', $options ) ) {
444 $options['limit'] = null;
445 }
446 if ( ! \array_key_exists( 'precision', $options ) ) {
447 $options['precision'] = null;
448 }
449 if ( ! \array_key_exists( 'scale', $options ) ) {
450 $options['scale'] = null;
451 }
452 $sql = \sprintf( 'ALTER TABLE %s ADD `%s` %s', $this->identifier( $table_name ), $column_name, $this->type_to_sql( $type, $options ) );
453 $sql .= $this->add_column_options( $type, $options );
454
455 return $this->execute_ddl( $sql );
456 }
457
458 /**
459 * Drops a column.
460 *
461 * @param string $table_name The table name.
462 * @param string $column_name The column name.
463 *
464 * @return bool
465 */
466 public function remove_column( $table_name, $column_name ) {
467 $sql = \sprintf( 'ALTER TABLE %s DROP COLUMN %s', $this->identifier( $table_name ), $this->identifier( $column_name ) );
468
469 return $this->execute_ddl( $sql );
470 }
471
472 /**
473 * Renames a column.
474 *
475 * @param string $table_name The table name.
476 * @param string $column_name The column name.
477 * @param string $new_column_name The new column name.
478 *
479 * @return bool
480 */
481 public function rename_column( $table_name, $column_name, $new_column_name ) {
482 if ( empty( $table_name ) || empty( $column_name ) || empty( $new_column_name ) ) {
483 return false;
484 }
485 $column_info = $this->column_info( $table_name, $column_name );
486 $current_type = $column_info['type'];
487 $sql = \sprintf( 'ALTER TABLE %s CHANGE %s %s %s', $this->identifier( $table_name ), $this->identifier( $column_name ), $this->identifier( $new_column_name ), $current_type );
488 $sql .= $this->add_column_options( $current_type, $column_info );
489
490 return $this->execute_ddl( $sql );
491 }
492
493 /**
494 * Changes a column.
495 *
496 * @param string $table_name The table name.
497 * @param string $column_name The column name.
498 * @param string $type The column type.
499 * @param array $options Column options.
500 *
501 * @return bool
502 */
503 public function change_column( $table_name, $column_name, $type, $options = [] ) {
504 if ( empty( $table_name ) || empty( $column_name ) || empty( $type ) ) {
505 return false;
506 }
507 $column_info = $this->column_info( $table_name, $column_name );
508 // Default types.
509 if ( ! \array_key_exists( 'limit', $options ) ) {
510 $options['limit'] = null;
511 }
512 if ( ! \array_key_exists( 'precision', $options ) ) {
513 $options['precision'] = null;
514 }
515 if ( ! \array_key_exists( 'scale', $options ) ) {
516 $options['scale'] = null;
517 }
518 $sql = \sprintf( 'ALTER TABLE `%s` CHANGE `%s` `%s` %s', $table_name, $column_name, $column_name, $this->type_to_sql( $type, $options ) );
519 $sql .= $this->add_column_options( $type, $options );
520
521 return $this->execute_ddl( $sql );
522 }
523
524 /**
525 * Returns the database information for a column.
526 *
527 * @param string $table The table name.
528 * @param string $column The column name.
529 *
530 * @return array|null
531 */
532 public function column_info( $table, $column ) {
533 if ( empty( $table ) || empty( $column ) ) {
534 return null;
535 }
536
537 try {
538 $sql = \sprintf( "SHOW FULL COLUMNS FROM %s LIKE '%s'", $this->identifier( $table ), $column );
539 $result = $this->select_one( $sql );
540 if ( \is_array( $result ) ) {
541 $result = \array_change_key_case( $result, \CASE_LOWER );
542 }
543
544 return $result;
545 } catch ( \Exception $e ) {
546 return null;
547 }
548 }
549
550 /**
551 * Adds an index.
552 *
553 * @param string $table_name The table name.
554 * @param array|string $column_name The column name(s).
555 * @param array $options Index options.
556 *
557 * @return bool
558 */
559 public function add_index( $table_name, $column_name, $options = [] ) {
560 if ( empty( $table_name ) || empty( $column_name ) ) {
561 return false;
562 }
563 // Unique index?
564 if ( \is_array( $options ) && \array_key_exists( 'unique', $options ) && $options['unique'] === true ) {
565 $unique = true;
566 }
567 else {
568 $unique = false;
569 }
570
571 // Did the user specify an index name?
572 if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) {
573 $index_name = $options['name'];
574 }
575 else {
576 $index_name = $this->get_index_name( $table_name, $column_name );
577 }
578
579 if ( \strlen( $index_name ) > Constants::MYSQL_MAX_IDENTIFIER_LENGTH ) {
580 return false;
581 }
582
583 if ( ! \is_array( $column_name ) ) {
584 $column_names = [ $column_name ];
585 }
586 else {
587 $column_names = $column_name;
588 }
589
590 $cols = [];
591 foreach ( $column_names as $name ) {
592 $cols[] = $this->identifier( $name );
593 }
594 $sql = \sprintf(
595 'CREATE %sINDEX %s ON %s(%s)',
596 ( $unique === true ) ? 'UNIQUE ' : '',
597 $this->identifier( $index_name ),
598 $this->identifier( $table_name ),
599 \implode( ', ', $cols )
600 );
601
602 return $this->execute_ddl( $sql );
603 }
604
605 /**
606 * Drops an index.
607 *
608 * @param string $table_name The table name.
609 * @param array|string $column_name The column name(s).
610 * @param array $options Index options.
611 *
612 * @return bool
613 */
614 public function remove_index( $table_name, $column_name, $options = [] ) {
615 if ( empty( $table_name ) || empty( $column_name ) ) {
616 return false;
617 }
618 // Did the user specify an index name?
619 if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) {
620 $index_name = $options['name'];
621 }
622 else {
623 $index_name = $this->get_index_name( $table_name, $column_name );
624 }
625
626 $sql = \sprintf( 'DROP INDEX %s ON %s', $this->identifier( $index_name ), $this->identifier( $table_name ) );
627
628 return $this->execute_ddl( $sql );
629 }
630
631 /**
632 * Adds timestamps.
633 *
634 * @param string $table_name The table name.
635 * @param string $created_column_name Created at column name.
636 * @param string $updated_column_name Updated at column name.
637 *
638 * @return bool
639 */
640 public function add_timestamps( $table_name, $created_column_name, $updated_column_name ) {
641 if ( empty( $table_name ) || empty( $created_column_name ) || empty( $updated_column_name ) ) {
642 return false;
643 }
644 $created_at = $this->add_column( $table_name, $created_column_name, 'datetime' );
645 $updated_at = $this->add_column(
646 $table_name,
647 $updated_column_name,
648 'timestamp',
649 [
650 'null' => false,
651 'default' => 'CURRENT_TIMESTAMP',
652 'extra' => 'ON UPDATE CURRENT_TIMESTAMP',
653 ]
654 );
655
656 return $created_at && $updated_at;
657 }
658
659 /**
660 * Removes timestamps.
661 *
662 * @param string $table_name The table name.
663 * @param string $created_column_name Created at column name.
664 * @param string $updated_column_name Updated at column name.
665 *
666 * @return bool Whether or not the timestamps were removed.
667 */
668 public function remove_timestamps( $table_name, $created_column_name, $updated_column_name ) {
669 if ( empty( $table_name ) || empty( $created_column_name ) || empty( $updated_column_name ) ) {
670 return false;
671 }
672 $updated_at = $this->remove_column( $table_name, $created_column_name );
673 $created_at = $this->remove_column( $table_name, $updated_column_name );
674
675 return $created_at && $updated_at;
676 }
677
678 /**
679 * Checks an index.
680 *
681 * @param string $table_name The table name.
682 * @param array|string $column_name The column name(s).
683 * @param array $options Index options.
684 *
685 * @return bool Whether or not the index exists.
686 */
687 public function has_index( $table_name, $column_name, $options = [] ) {
688 if ( empty( $table_name ) || empty( $column_name ) ) {
689 return false;
690 }
691 // Did the user specify an index name?
692 if ( \is_array( $options ) && \array_key_exists( 'name', $options ) ) {
693 $index_name = $options['name'];
694 }
695 else {
696 $index_name = $this->get_index_name( $table_name, $column_name );
697 }
698 $indexes = $this->indexes( $table_name );
699 foreach ( $indexes as $idx ) {
700 if ( $idx['name'] === $index_name ) {
701 return true;
702 }
703 }
704
705 return false;
706 }
707
708 /**
709 * Returns all indexes of a table.
710 *
711 * @param string $table_name The table name.
712 *
713 * @return array
714 */
715 public function indexes( $table_name ) {
716 $sql = \sprintf( 'SHOW KEYS FROM %s', $this->identifier( $table_name ) );
717 $result = $this->select_all( $sql );
718 $indexes = [];
719 foreach ( $result as $row ) {
720 // Skip primary.
721 if ( $row['Key_name'] === 'PRIMARY' ) {
722 continue;
723 }
724 $indexes[] = [
725 'name' => $row['Key_name'],
726 'unique' => (int) $row['Non_unique'] === 0,
727 ];
728 }
729
730 return $indexes;
731 }
732
733 /**
734 * Converts a type to sql. Default options:
735 * $limit = null, $precision = null, $scale = null
736 *
737 * @param string $type The native type.
738 * @param array $options The options.
739 *
740 * @return string The SQL type.
741 *
742 * @throws Exception If invalid arguments are supplied.
743 */
744 public function type_to_sql( $type, $options = [] ) {
745 $natives = $this->native_database_types();
746 if ( ! \array_key_exists( $type, $natives ) ) {
747 $error = \sprintf( "Error:I dont know what column type of '%s' maps to for MySQL.", $type );
748 $error .= "\nYou provided: {$type}\n";
749 $error .= "Valid types are: \n";
750 $types = \array_keys( $natives );
751 foreach ( $types as $t ) {
752 if ( $t === 'primary_key' ) {
753 continue;
754 }
755 $error .= "\t{$t}\n";
756 }
757 throw new Exception( $error );
758 }
759 $scale = null;
760 $precision = null;
761 $limit = null;
762 if ( isset( $options['precision'] ) ) {
763 $precision = $options['precision'];
764 }
765 if ( isset( $options['scale'] ) ) {
766 $scale = $options['scale'];
767 }
768 if ( isset( $options['limit'] ) ) {
769 $limit = $options['limit'];
770 }
771 if ( isset( $options['values'] ) ) {
772 $values = $options['values'];
773 }
774 $native_type = $natives[ $type ];
775 if ( \is_array( $native_type ) && \array_key_exists( 'name', $native_type ) ) {
776 $column_type_sql = $native_type['name'];
777 }
778 else {
779 return $native_type;
780 }
781 if ( $type === 'decimal' || $type === 'float' ) {
782 // Ignore limit, use precison and scale.
783 if ( $precision === null && \array_key_exists( 'precision', $native_type ) ) {
784 $precision = $native_type['precision'];
785 }
786 if ( $scale === null && \array_key_exists( 'scale', $native_type ) ) {
787 $scale = $native_type['scale'];
788 }
789 if ( $precision !== null ) {
790 if ( \is_int( $scale ) ) {
791 $column_type_sql .= \sprintf( '(%d, %d)', $precision, $scale );
792 }
793 else {
794 $column_type_sql .= \sprintf( '(%d)', $precision );
795 }
796 }
797 else {
798 if ( $scale ) {
799 throw new Exception( "Error adding $type column: precision cannot be empty if scale is specified" );
800 }
801 }
802 }
803 elseif ( $type === 'enum' ) {
804 if ( empty( $values ) ) {
805 throw new Exception( 'Error adding enum column: there must be at least one value defined' );
806 }
807 else {
808 $column_type_sql .= \sprintf(
809 "('%s')",
810 \implode( "','", \array_map( [ $this, 'quote_string' ], $values ) )
811 );
812 }
813 }
814 // Not a decimal column.
815 if ( $limit === null && \array_key_exists( 'limit', $native_type ) ) {
816 $limit = $native_type['limit'];
817 }
818 if ( $limit ) {
819 $column_type_sql .= \sprintf( '(%d)', $limit );
820 }
821
822 return $column_type_sql;
823 }
824
825 /**
826 * Adds column options.
827 *
828 * @param string $type The native type.
829 * @param array $options The options.
830 *
831 * @return string The SQL statement for the column options.
832 *
833 * @throws Exception If invalid arguments are supplied.
834 */
835 public function add_column_options( $type, $options ) {
836 $sql = '';
837 if ( ! \is_array( $options ) ) {
838 return $sql;
839 }
840 if ( \array_key_exists( 'unsigned', $options ) && $options['unsigned'] === true ) {
841 $sql .= ' UNSIGNED';
842 }
843 if ( \array_key_exists( 'character', $options ) ) {
844 $sql .= \sprintf( ' CHARACTER SET %s', $this->identifier( $options['character'] ) );
845 }
846 if ( \array_key_exists( 'collate', $options ) ) {
847 $sql .= \sprintf( ' COLLATE %s', $this->identifier( $options['collate'] ) );
848 }
849 if ( \array_key_exists( 'auto_increment', $options ) && $options['auto_increment'] === true ) {
850 $sql .= ' auto_increment';
851 }
852 if ( \array_key_exists( 'default', $options ) && $options['default'] !== null ) {
853 if ( $this->is_sql_method_call( $options['default'] ) ) {
854 throw new Exception( 'MySQL does not support function calls as default values, constants only.' );
855 }
856 if ( \is_int( $options['default'] ) ) {
857 $default_format = '%d';
858 }
859 elseif ( \is_bool( $options['default'] ) ) {
860 $default_format = "'%d'";
861 }
862 elseif ( $options['default'] === 'CURRENT_TIMESTAMP' ) {
863 $default_format = '%s';
864 }
865 else {
866 $default_format = "'%s'";
867 }
868 $default_value = \sprintf( $default_format, $options['default'] );
869 $sql .= \sprintf( ' DEFAULT %s', $default_value );
870 }
871 if ( \array_key_exists( 'null', $options ) ) {
872 if ( $options['null'] === false || $options['null'] === 'NO' ) {
873 $sql .= ' NOT NULL';
874 }
875 elseif ( $type === 'timestamp' ) {
876 $sql .= ' NULL';
877 }
878 }
879 if ( \array_key_exists( 'comment', $options ) ) {
880 $sql .= \sprintf( " COMMENT '%s'", $this->quote_string( $options['comment'] ) );
881 }
882 if ( \array_key_exists( 'extra', $options ) ) {
883 $sql .= \sprintf( ' %s', $this->quote_string( $options['extra'] ) );
884 }
885 if ( \array_key_exists( 'after', $options ) ) {
886 $sql .= \sprintf( ' AFTER %s', $this->identifier( $options['after'] ) );
887 }
888
889 return $sql;
890 }
891
892 /**
893 * Returns a list of all versions that have been migrated.
894 *
895 * @return string[] The version numbers that have been migrated.
896 */
897 public function get_migrated_versions() {
898 $result = $this->select_all( \sprintf( 'SELECT version FROM %s', $this->get_schema_version_table_name() ) );
899 return \array_column( $result, 'version' );
900 }
901
902 /**
903 * Adds a migrated version.
904 *
905 * @param string $version The version.
906 *
907 * @return bool Whether or not the version was succesfully set.
908 */
909 public function add_version( $version ) {
910 $sql = \sprintf( "INSERT INTO %s (version) VALUES ('%s')", $this->get_schema_version_table_name(), $version );
911
912 return $this->execute_ddl( $sql );
913 }
914
915 /**
916 * Removes a migrated version.
917 *
918 * @param string $version The version.
919 *
920 * @return bool Whether or not the version was succesfully removed.
921 */
922 public function remove_version( $version ) {
923 $sql = \sprintf( "DELETE FROM %s WHERE version = '%s'", $this->get_schema_version_table_name(), $version );
924
925 return $this->execute_ddl( $sql );
926 }
927
928 /**
929 * Returns a message displaying the current version
930 *
931 * @return string
932 */
933 public function __toString() {
934 return self::class . ', version ' . $this->version;
935 }
936
937 /**
938 * Returns an index name.
939 *
940 * @param string $table_name The table name.
941 * @param string $column_name The column name.
942 *
943 * @return string The index name.
944 */
945 private function get_index_name( $table_name, $column_name ) {
946 $name = \preg_replace( '/\\W/', '_', $table_name );
947 $name = \preg_replace( '/\\_{2,}/', '_', $name );
948 // If the column parameter is an array then the user wants to create a multi-column index.
949 if ( \is_array( $column_name ) ) {
950 $column_str = \implode( '_and_', $column_name );
951 }
952 else {
953 $column_str = $column_name;
954 }
955 $name .= \sprintf( '_%s', $column_str );
956 return $name;
957 }
958
959 /**
960 * Returns the type of a query.
961 *
962 * @param string $query The query to run.
963 *
964 * @return int The query type.
965 */
966 private function determine_query_type( $query ) {
967 $query = \strtolower( \trim( $query ) );
968 $match = [];
969 \preg_match( '/^(\\w)*/i', $query, $match );
970 $type = $match[0];
971 switch ( $type ) {
972 case 'select':
973 return Constants::SQL_SELECT;
974 case 'update':
975 return Constants::SQL_UPDATE;
976 case 'delete':
977 return Constants::SQL_DELETE;
978 case 'insert':
979 return Constants::SQL_INSERT;
980 case 'alter':
981 return Constants::SQL_ALTER;
982 case 'drop':
983 return Constants::SQL_DROP;
984 case 'create':
985 return Constants::SQL_CREATE;
986 case 'show':
987 return Constants::SQL_SHOW;
988 case 'rename':
989 return Constants::SQL_RENAME;
990 case 'set':
991 return Constants::SQL_SET;
992 default:
993 return Constants::SQL_UNKNOWN_QUERY_TYPE;
994 }
995 }
996
997 /**
998 * Detect whether or not the string represents a function call and if so
999 * do not wrap it in single-quotes, otherwise do wrap in single quotes.
1000 *
1001 * @param string $text The string.
1002 *
1003 * @return bool Whether or not it's a SQL function call.
1004 */
1005 private function is_sql_method_call( $text ) {
1006 $text = \trim( $text );
1007 if ( \substr( $text, -2, 2 ) === '()' ) {
1008 return true;
1009 }
1010 return false;
1011 }
1012
1013 /**
1014 * Checks if a transaction is active.
1015 *
1016 * @return bool
1017 */
1018 private function in_transaction() {
1019 return $this->in_transaction;
1020 }
1021
1022 /**
1023 * Starts a transaction.
1024 *
1025 * @return void
1026 *
1027 * @throws Exception If a transaction was already started.
1028 */
1029 private function begin_transaction() {
1030 global $wpdb;
1031
1032 if ( $this->in_transaction === true ) {
1033 throw new Exception( 'Transaction already started' );
1034 }
1035 $wpdb->query( 'START TRANSACTION' );
1036 $this->in_transaction = true;
1037 }
1038
1039 /**
1040 * Commits a transaction.
1041 *
1042 * @return void
1043 *
1044 * @throws Exception If no transaction was strated.
1045 */
1046 private function commit() {
1047 global $wpdb;
1048
1049 if ( $this->in_transaction === false ) {
1050 throw new Exception( 'Transaction not started' );
1051 }
1052 $wpdb->query( 'COMMIT' );
1053 $this->in_transaction = false;
1054 }
1055
1056 /**
1057 * Rollbacks a transaction.
1058 *
1059 * @return void
1060 *
1061 * @throws Exception If no transaction was started.
1062 */
1063 private function rollback() {
1064 global $wpdb;
1065
1066 if ( $this->in_transaction === false ) {
1067 throw new Exception( 'Transaction not started' );
1068 }
1069 $wpdb->query( 'ROLLBACK' );
1070 $this->in_transaction = false;
1071 }
1072 }
1073