PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.5
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.5
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 28.5, at lib/migrations/adapter.php

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