1<?php 2 3namespace Doctrine\DBAL\Platforms; 4 5use Doctrine\Common\EventManager; 6use Doctrine\DBAL\DBALException; 7use Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs; 8use Doctrine\DBAL\Event\SchemaAlterTableChangeColumnEventArgs; 9use Doctrine\DBAL\Event\SchemaAlterTableEventArgs; 10use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs; 11use Doctrine\DBAL\Event\SchemaAlterTableRenameColumnEventArgs; 12use Doctrine\DBAL\Event\SchemaCreateTableColumnEventArgs; 13use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; 14use Doctrine\DBAL\Event\SchemaDropTableEventArgs; 15use Doctrine\DBAL\Events; 16use Doctrine\DBAL\Platforms\Keywords\KeywordList; 17use Doctrine\DBAL\Schema\Column; 18use Doctrine\DBAL\Schema\ColumnDiff; 19use Doctrine\DBAL\Schema\Constraint; 20use Doctrine\DBAL\Schema\ForeignKeyConstraint; 21use Doctrine\DBAL\Schema\Identifier; 22use Doctrine\DBAL\Schema\Index; 23use Doctrine\DBAL\Schema\Sequence; 24use Doctrine\DBAL\Schema\Table; 25use Doctrine\DBAL\Schema\TableDiff; 26use Doctrine\DBAL\TransactionIsolationLevel; 27use Doctrine\DBAL\Types; 28use Doctrine\DBAL\Types\Type; 29use InvalidArgumentException; 30use UnexpectedValueException; 31 32use function addcslashes; 33use function array_map; 34use function array_merge; 35use function array_unique; 36use function array_values; 37use function assert; 38use function count; 39use function explode; 40use function func_get_arg; 41use function func_get_args; 42use function func_num_args; 43use function implode; 44use function in_array; 45use function is_array; 46use function is_bool; 47use function is_int; 48use function is_string; 49use function preg_quote; 50use function preg_replace; 51use function sprintf; 52use function str_replace; 53use function strlen; 54use function strpos; 55use function strtolower; 56use function strtoupper; 57use function trigger_error; 58 59use const E_USER_DEPRECATED; 60 61/** 62 * Base class for all DatabasePlatforms. The DatabasePlatforms are the central 63 * point of abstraction of platform-specific behaviors, features and SQL dialects. 64 * They are a passive source of information. 65 * 66 * @todo Remove any unnecessary methods. 67 */ 68abstract class AbstractPlatform 69{ 70 public const CREATE_INDEXES = 1; 71 72 public const CREATE_FOREIGNKEYS = 2; 73 74 /** 75 * @deprecated Use DateIntervalUnit::INTERVAL_UNIT_SECOND. 76 */ 77 public const DATE_INTERVAL_UNIT_SECOND = DateIntervalUnit::SECOND; 78 79 /** 80 * @deprecated Use DateIntervalUnit::MINUTE. 81 */ 82 public const DATE_INTERVAL_UNIT_MINUTE = DateIntervalUnit::MINUTE; 83 84 /** 85 * @deprecated Use DateIntervalUnit::HOUR. 86 */ 87 public const DATE_INTERVAL_UNIT_HOUR = DateIntervalUnit::HOUR; 88 89 /** 90 * @deprecated Use DateIntervalUnit::DAY. 91 */ 92 public const DATE_INTERVAL_UNIT_DAY = DateIntervalUnit::DAY; 93 94 /** 95 * @deprecated Use DateIntervalUnit::WEEK. 96 */ 97 public const DATE_INTERVAL_UNIT_WEEK = DateIntervalUnit::WEEK; 98 99 /** 100 * @deprecated Use DateIntervalUnit::MONTH. 101 */ 102 public const DATE_INTERVAL_UNIT_MONTH = DateIntervalUnit::MONTH; 103 104 /** 105 * @deprecated Use DateIntervalUnit::QUARTER. 106 */ 107 public const DATE_INTERVAL_UNIT_QUARTER = DateIntervalUnit::QUARTER; 108 109 /** 110 * @deprecated Use DateIntervalUnit::QUARTER. 111 */ 112 public const DATE_INTERVAL_UNIT_YEAR = DateIntervalUnit::YEAR; 113 114 /** 115 * @deprecated Use TrimMode::UNSPECIFIED. 116 */ 117 public const TRIM_UNSPECIFIED = TrimMode::UNSPECIFIED; 118 119 /** 120 * @deprecated Use TrimMode::LEADING. 121 */ 122 public const TRIM_LEADING = TrimMode::LEADING; 123 124 /** 125 * @deprecated Use TrimMode::TRAILING. 126 */ 127 public const TRIM_TRAILING = TrimMode::TRAILING; 128 129 /** 130 * @deprecated Use TrimMode::BOTH. 131 */ 132 public const TRIM_BOTH = TrimMode::BOTH; 133 134 /** @var string[]|null */ 135 protected $doctrineTypeMapping = null; 136 137 /** 138 * Contains a list of all columns that should generate parseable column comments for type-detection 139 * in reverse engineering scenarios. 140 * 141 * @var string[]|null 142 */ 143 protected $doctrineTypeComments = null; 144 145 /** @var EventManager */ 146 protected $_eventManager; 147 148 /** 149 * Holds the KeywordList instance for the current platform. 150 * 151 * @var KeywordList|null 152 */ 153 protected $_keywords; 154 155 public function __construct() 156 { 157 } 158 159 /** 160 * Sets the EventManager used by the Platform. 161 * 162 * @return void 163 */ 164 public function setEventManager(EventManager $eventManager) 165 { 166 $this->_eventManager = $eventManager; 167 } 168 169 /** 170 * Gets the EventManager used by the Platform. 171 * 172 * @return EventManager 173 */ 174 public function getEventManager() 175 { 176 return $this->_eventManager; 177 } 178 179 /** 180 * Returns the SQL snippet that declares a boolean column. 181 * 182 * @param mixed[] $column 183 * 184 * @return string 185 */ 186 abstract public function getBooleanTypeDeclarationSQL(array $column); 187 188 /** 189 * Returns the SQL snippet that declares a 4 byte integer column. 190 * 191 * @param mixed[] $column 192 * 193 * @return string 194 */ 195 abstract public function getIntegerTypeDeclarationSQL(array $column); 196 197 /** 198 * Returns the SQL snippet that declares an 8 byte integer column. 199 * 200 * @param mixed[] $column 201 * 202 * @return string 203 */ 204 abstract public function getBigIntTypeDeclarationSQL(array $column); 205 206 /** 207 * Returns the SQL snippet that declares a 2 byte integer column. 208 * 209 * @param mixed[] $column 210 * 211 * @return string 212 */ 213 abstract public function getSmallIntTypeDeclarationSQL(array $column); 214 215 /** 216 * Returns the SQL snippet that declares common properties of an integer column. 217 * 218 * @param mixed[] $column 219 * 220 * @return string 221 */ 222 abstract protected function _getCommonIntegerTypeDeclarationSQL(array $column); 223 224 /** 225 * Lazy load Doctrine Type Mappings. 226 * 227 * @return void 228 */ 229 abstract protected function initializeDoctrineTypeMappings(); 230 231 /** 232 * Initializes Doctrine Type Mappings with the platform defaults 233 * and with all additional type mappings. 234 * 235 * @return void 236 */ 237 private function initializeAllDoctrineTypeMappings() 238 { 239 $this->initializeDoctrineTypeMappings(); 240 241 foreach (Type::getTypesMap() as $typeName => $className) { 242 foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) { 243 $this->doctrineTypeMapping[$dbType] = $typeName; 244 } 245 } 246 } 247 248 /** 249 * Returns the SQL snippet used to declare a VARCHAR column type. 250 * 251 * @param mixed[] $column 252 * 253 * @return string 254 */ 255 public function getVarcharTypeDeclarationSQL(array $column) 256 { 257 if (! isset($column['length'])) { 258 $column['length'] = $this->getVarcharDefaultLength(); 259 } 260 261 $fixed = $column['fixed'] ?? false; 262 263 $maxLength = $fixed 264 ? $this->getCharMaxLength() 265 : $this->getVarcharMaxLength(); 266 267 if ($column['length'] > $maxLength) { 268 return $this->getClobTypeDeclarationSQL($column); 269 } 270 271 return $this->getVarcharTypeDeclarationSQLSnippet($column['length'], $fixed); 272 } 273 274 /** 275 * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. 276 * 277 * @param mixed[] $column The column definition. 278 * 279 * @return string 280 */ 281 public function getBinaryTypeDeclarationSQL(array $column) 282 { 283 if (! isset($column['length'])) { 284 $column['length'] = $this->getBinaryDefaultLength(); 285 } 286 287 $fixed = $column['fixed'] ?? false; 288 289 $maxLength = $this->getBinaryMaxLength(); 290 291 if ($column['length'] > $maxLength) { 292 if ($maxLength > 0) { 293 @trigger_error(sprintf( 294 'Binary column length %d is greater than supported by the platform (%d).' 295 . ' Reduce the column length or use a BLOB column instead.', 296 $column['length'], 297 $maxLength 298 ), E_USER_DEPRECATED); 299 } 300 301 return $this->getBlobTypeDeclarationSQL($column); 302 } 303 304 return $this->getBinaryTypeDeclarationSQLSnippet($column['length'], $fixed); 305 } 306 307 /** 308 * Returns the SQL snippet to declare a GUID/UUID column. 309 * 310 * By default this maps directly to a CHAR(36) and only maps to more 311 * special datatypes when the underlying databases support this datatype. 312 * 313 * @param mixed[] $column 314 * 315 * @return string 316 */ 317 public function getGuidTypeDeclarationSQL(array $column) 318 { 319 $column['length'] = 36; 320 $column['fixed'] = true; 321 322 return $this->getVarcharTypeDeclarationSQL($column); 323 } 324 325 /** 326 * Returns the SQL snippet to declare a JSON column. 327 * 328 * By default this maps directly to a CLOB and only maps to more 329 * special datatypes when the underlying databases support this datatype. 330 * 331 * @param mixed[] $column 332 * 333 * @return string 334 */ 335 public function getJsonTypeDeclarationSQL(array $column) 336 { 337 return $this->getClobTypeDeclarationSQL($column); 338 } 339 340 /** 341 * @param int $length 342 * @param bool $fixed 343 * 344 * @return string 345 * 346 * @throws DBALException If not supported on this platform. 347 */ 348 protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) 349 { 350 throw DBALException::notSupported('VARCHARs not supported by Platform.'); 351 } 352 353 /** 354 * Returns the SQL snippet used to declare a BINARY/VARBINARY column type. 355 * 356 * @param int $length The length of the column. 357 * @param bool $fixed Whether the column length is fixed. 358 * 359 * @return string 360 * 361 * @throws DBALException If not supported on this platform. 362 */ 363 protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed) 364 { 365 throw DBALException::notSupported('BINARY/VARBINARY column types are not supported by this platform.'); 366 } 367 368 /** 369 * Returns the SQL snippet used to declare a CLOB column type. 370 * 371 * @param mixed[] $column 372 * 373 * @return string 374 */ 375 abstract public function getClobTypeDeclarationSQL(array $column); 376 377 /** 378 * Returns the SQL Snippet used to declare a BLOB column type. 379 * 380 * @param mixed[] $column 381 * 382 * @return string 383 */ 384 abstract public function getBlobTypeDeclarationSQL(array $column); 385 386 /** 387 * Gets the name of the platform. 388 * 389 * @return string 390 */ 391 abstract public function getName(); 392 393 /** 394 * Registers a doctrine type to be used in conjunction with a column type of this platform. 395 * 396 * @param string $dbType 397 * @param string $doctrineType 398 * 399 * @return void 400 * 401 * @throws DBALException If the type is not found. 402 */ 403 public function registerDoctrineTypeMapping($dbType, $doctrineType) 404 { 405 if ($this->doctrineTypeMapping === null) { 406 $this->initializeAllDoctrineTypeMappings(); 407 } 408 409 if (! Types\Type::hasType($doctrineType)) { 410 throw DBALException::typeNotFound($doctrineType); 411 } 412 413 $dbType = strtolower($dbType); 414 $this->doctrineTypeMapping[$dbType] = $doctrineType; 415 416 $doctrineType = Type::getType($doctrineType); 417 418 if (! $doctrineType->requiresSQLCommentHint($this)) { 419 return; 420 } 421 422 $this->markDoctrineTypeCommented($doctrineType); 423 } 424 425 /** 426 * Gets the Doctrine type that is mapped for the given database column type. 427 * 428 * @param string $dbType 429 * 430 * @return string 431 * 432 * @throws DBALException 433 */ 434 public function getDoctrineTypeMapping($dbType) 435 { 436 if ($this->doctrineTypeMapping === null) { 437 $this->initializeAllDoctrineTypeMappings(); 438 } 439 440 $dbType = strtolower($dbType); 441 442 if (! isset($this->doctrineTypeMapping[$dbType])) { 443 throw new DBALException( 444 'Unknown database type ' . $dbType . ' requested, ' . static::class . ' may not support it.' 445 ); 446 } 447 448 return $this->doctrineTypeMapping[$dbType]; 449 } 450 451 /** 452 * Checks if a database type is currently supported by this platform. 453 * 454 * @param string $dbType 455 * 456 * @return bool 457 */ 458 public function hasDoctrineTypeMappingFor($dbType) 459 { 460 if ($this->doctrineTypeMapping === null) { 461 $this->initializeAllDoctrineTypeMappings(); 462 } 463 464 $dbType = strtolower($dbType); 465 466 return isset($this->doctrineTypeMapping[$dbType]); 467 } 468 469 /** 470 * Initializes the Doctrine Type comments instance variable for in_array() checks. 471 * 472 * @return void 473 */ 474 protected function initializeCommentedDoctrineTypes() 475 { 476 $this->doctrineTypeComments = []; 477 478 foreach (Type::getTypesMap() as $typeName => $className) { 479 $type = Type::getType($typeName); 480 481 if (! $type->requiresSQLCommentHint($this)) { 482 continue; 483 } 484 485 $this->doctrineTypeComments[] = $typeName; 486 } 487 } 488 489 /** 490 * Is it necessary for the platform to add a parsable type comment to allow reverse engineering the given type? 491 * 492 * @return bool 493 */ 494 public function isCommentedDoctrineType(Type $doctrineType) 495 { 496 if ($this->doctrineTypeComments === null) { 497 $this->initializeCommentedDoctrineTypes(); 498 } 499 500 assert(is_array($this->doctrineTypeComments)); 501 502 return in_array($doctrineType->getName(), $this->doctrineTypeComments); 503 } 504 505 /** 506 * Marks this type as to be commented in ALTER TABLE and CREATE TABLE statements. 507 * 508 * @param string|Type $doctrineType 509 * 510 * @return void 511 */ 512 public function markDoctrineTypeCommented($doctrineType) 513 { 514 if ($this->doctrineTypeComments === null) { 515 $this->initializeCommentedDoctrineTypes(); 516 } 517 518 assert(is_array($this->doctrineTypeComments)); 519 520 $this->doctrineTypeComments[] = $doctrineType instanceof Type ? $doctrineType->getName() : $doctrineType; 521 } 522 523 /** 524 * Gets the comment to append to a column comment that helps parsing this type in reverse engineering. 525 * 526 * @return string 527 */ 528 public function getDoctrineTypeComment(Type $doctrineType) 529 { 530 return '(DC2Type:' . $doctrineType->getName() . ')'; 531 } 532 533 /** 534 * Gets the comment of a passed column modified by potential doctrine type comment hints. 535 * 536 * @return string|null 537 */ 538 protected function getColumnComment(Column $column) 539 { 540 $comment = $column->getComment(); 541 542 if ($this->isCommentedDoctrineType($column->getType())) { 543 $comment .= $this->getDoctrineTypeComment($column->getType()); 544 } 545 546 return $comment; 547 } 548 549 /** 550 * Gets the character used for identifier quoting. 551 * 552 * @return string 553 */ 554 public function getIdentifierQuoteCharacter() 555 { 556 return '"'; 557 } 558 559 /** 560 * Gets the string portion that starts an SQL comment. 561 * 562 * @return string 563 */ 564 public function getSqlCommentStartString() 565 { 566 return '--'; 567 } 568 569 /** 570 * Gets the string portion that ends an SQL comment. 571 * 572 * @return string 573 */ 574 public function getSqlCommentEndString() 575 { 576 return "\n"; 577 } 578 579 /** 580 * Gets the maximum length of a char column. 581 */ 582 public function getCharMaxLength(): int 583 { 584 return $this->getVarcharMaxLength(); 585 } 586 587 /** 588 * Gets the maximum length of a varchar column. 589 * 590 * @return int 591 */ 592 public function getVarcharMaxLength() 593 { 594 return 4000; 595 } 596 597 /** 598 * Gets the default length of a varchar column. 599 * 600 * @return int 601 */ 602 public function getVarcharDefaultLength() 603 { 604 return 255; 605 } 606 607 /** 608 * Gets the maximum length of a binary column. 609 * 610 * @return int 611 */ 612 public function getBinaryMaxLength() 613 { 614 return 4000; 615 } 616 617 /** 618 * Gets the default length of a binary column. 619 * 620 * @return int 621 */ 622 public function getBinaryDefaultLength() 623 { 624 return 255; 625 } 626 627 /** 628 * Gets all SQL wildcard characters of the platform. 629 * 630 * @return string[] 631 */ 632 public function getWildcards() 633 { 634 return ['%', '_']; 635 } 636 637 /** 638 * Returns the regular expression operator. 639 * 640 * @return string 641 * 642 * @throws DBALException If not supported on this platform. 643 */ 644 public function getRegexpExpression() 645 { 646 throw DBALException::notSupported(__METHOD__); 647 } 648 649 /** 650 * Returns the global unique identifier expression. 651 * 652 * @deprecated Use application-generated UUIDs instead 653 * 654 * @return string 655 * 656 * @throws DBALException If not supported on this platform. 657 */ 658 public function getGuidExpression() 659 { 660 throw DBALException::notSupported(__METHOD__); 661 } 662 663 /** 664 * Returns the SQL snippet to get the average value of a column. 665 * 666 * @param string $column The column to use. 667 * 668 * @return string Generated SQL including an AVG aggregate function. 669 */ 670 public function getAvgExpression($column) 671 { 672 return 'AVG(' . $column . ')'; 673 } 674 675 /** 676 * Returns the SQL snippet to get the number of rows (without a NULL value) of a column. 677 * 678 * If a '*' is used instead of a column the number of selected rows is returned. 679 * 680 * @param string|int $column The column to use. 681 * 682 * @return string Generated SQL including a COUNT aggregate function. 683 */ 684 public function getCountExpression($column) 685 { 686 return 'COUNT(' . $column . ')'; 687 } 688 689 /** 690 * Returns the SQL snippet to get the highest value of a column. 691 * 692 * @param string $column The column to use. 693 * 694 * @return string Generated SQL including a MAX aggregate function. 695 */ 696 public function getMaxExpression($column) 697 { 698 return 'MAX(' . $column . ')'; 699 } 700 701 /** 702 * Returns the SQL snippet to get the lowest value of a column. 703 * 704 * @param string $column The column to use. 705 * 706 * @return string Generated SQL including a MIN aggregate function. 707 */ 708 public function getMinExpression($column) 709 { 710 return 'MIN(' . $column . ')'; 711 } 712 713 /** 714 * Returns the SQL snippet to get the total sum of a column. 715 * 716 * @param string $column The column to use. 717 * 718 * @return string Generated SQL including a SUM aggregate function. 719 */ 720 public function getSumExpression($column) 721 { 722 return 'SUM(' . $column . ')'; 723 } 724 725 // scalar functions 726 727 /** 728 * Returns the SQL snippet to get the md5 sum of a column. 729 * 730 * Note: Not SQL92, but common functionality. 731 * 732 * @param string $column 733 * 734 * @return string 735 */ 736 public function getMd5Expression($column) 737 { 738 return 'MD5(' . $column . ')'; 739 } 740 741 /** 742 * Returns the SQL snippet to get the length of a text column. 743 * 744 * @param string $column 745 * 746 * @return string 747 */ 748 public function getLengthExpression($column) 749 { 750 return 'LENGTH(' . $column . ')'; 751 } 752 753 /** 754 * Returns the SQL snippet to get the squared value of a column. 755 * 756 * @param string $column The column to use. 757 * 758 * @return string Generated SQL including an SQRT aggregate function. 759 */ 760 public function getSqrtExpression($column) 761 { 762 return 'SQRT(' . $column . ')'; 763 } 764 765 /** 766 * Returns the SQL snippet to round a numeric column to the number of decimals specified. 767 * 768 * @param string $column 769 * @param int $decimals 770 * 771 * @return string 772 */ 773 public function getRoundExpression($column, $decimals = 0) 774 { 775 return 'ROUND(' . $column . ', ' . $decimals . ')'; 776 } 777 778 /** 779 * Returns the SQL snippet to get the remainder of the division operation $expression1 / $expression2. 780 * 781 * @param string $expression1 782 * @param string $expression2 783 * 784 * @return string 785 */ 786 public function getModExpression($expression1, $expression2) 787 { 788 return 'MOD(' . $expression1 . ', ' . $expression2 . ')'; 789 } 790 791 /** 792 * Returns the SQL snippet to trim a string. 793 * 794 * @param string $str The expression to apply the trim to. 795 * @param int $mode The position of the trim (leading/trailing/both). 796 * @param string|bool $char The char to trim, has to be quoted already. Defaults to space. 797 * 798 * @return string 799 */ 800 public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false) 801 { 802 $expression = ''; 803 804 switch ($mode) { 805 case TrimMode::LEADING: 806 $expression = 'LEADING '; 807 break; 808 809 case TrimMode::TRAILING: 810 $expression = 'TRAILING '; 811 break; 812 813 case TrimMode::BOTH: 814 $expression = 'BOTH '; 815 break; 816 } 817 818 if ($char !== false) { 819 $expression .= $char . ' '; 820 } 821 822 if ($mode || $char !== false) { 823 $expression .= 'FROM '; 824 } 825 826 return 'TRIM(' . $expression . $str . ')'; 827 } 828 829 /** 830 * Returns the SQL snippet to trim trailing space characters from the expression. 831 * 832 * @param string $str Literal string or column name. 833 * 834 * @return string 835 */ 836 public function getRtrimExpression($str) 837 { 838 return 'RTRIM(' . $str . ')'; 839 } 840 841 /** 842 * Returns the SQL snippet to trim leading space characters from the expression. 843 * 844 * @param string $str Literal string or column name. 845 * 846 * @return string 847 */ 848 public function getLtrimExpression($str) 849 { 850 return 'LTRIM(' . $str . ')'; 851 } 852 853 /** 854 * Returns the SQL snippet to change all characters from the expression to uppercase, 855 * according to the current character set mapping. 856 * 857 * @param string $str Literal string or column name. 858 * 859 * @return string 860 */ 861 public function getUpperExpression($str) 862 { 863 return 'UPPER(' . $str . ')'; 864 } 865 866 /** 867 * Returns the SQL snippet to change all characters from the expression to lowercase, 868 * according to the current character set mapping. 869 * 870 * @param string $str Literal string or column name. 871 * 872 * @return string 873 */ 874 public function getLowerExpression($str) 875 { 876 return 'LOWER(' . $str . ')'; 877 } 878 879 /** 880 * Returns the SQL snippet to get the position of the first occurrence of substring $substr in string $str. 881 * 882 * @param string $str Literal string. 883 * @param string $substr Literal string to find. 884 * @param int|false $startPos Position to start at, beginning of string by default. 885 * 886 * @return string 887 * 888 * @throws DBALException If not supported on this platform. 889 */ 890 public function getLocateExpression($str, $substr, $startPos = false) 891 { 892 throw DBALException::notSupported(__METHOD__); 893 } 894 895 /** 896 * Returns the SQL snippet to get the current system date. 897 * 898 * @return string 899 */ 900 public function getNowExpression() 901 { 902 return 'NOW()'; 903 } 904 905 /** 906 * Returns a SQL snippet to get a substring inside an SQL statement. 907 * 908 * Note: Not SQL92, but common functionality. 909 * 910 * SQLite only supports the 2 parameter variant of this function. 911 * 912 * @param string $string An sql string literal or column name/alias. 913 * @param int $start Where to start the substring portion. 914 * @param int|null $length The substring portion length. 915 * 916 * @return string 917 */ 918 public function getSubstringExpression($string, $start, $length = null) 919 { 920 if ($length === null) { 921 return 'SUBSTRING(' . $string . ' FROM ' . $start . ')'; 922 } 923 924 return 'SUBSTRING(' . $string . ' FROM ' . $start . ' FOR ' . $length . ')'; 925 } 926 927 /** 928 * Returns a SQL snippet to concatenate the given expressions. 929 * 930 * Accepts an arbitrary number of string parameters. Each parameter must contain an expression. 931 * 932 * @return string 933 */ 934 public function getConcatExpression() 935 { 936 return implode(' || ', func_get_args()); 937 } 938 939 /** 940 * Returns the SQL for a logical not. 941 * 942 * Example: 943 * <code> 944 * $q = new Doctrine_Query(); 945 * $e = $q->expr; 946 * $q->select('*')->from('table') 947 * ->where($e->eq('id', $e->not('null')); 948 * </code> 949 * 950 * @param string $expression 951 * 952 * @return string The logical expression. 953 */ 954 public function getNotExpression($expression) 955 { 956 return 'NOT(' . $expression . ')'; 957 } 958 959 /** 960 * Returns the SQL that checks if an expression is null. 961 * 962 * @param string $expression The expression that should be compared to null. 963 * 964 * @return string The logical expression. 965 */ 966 public function getIsNullExpression($expression) 967 { 968 return $expression . ' IS NULL'; 969 } 970 971 /** 972 * Returns the SQL that checks if an expression is not null. 973 * 974 * @param string $expression The expression that should be compared to null. 975 * 976 * @return string The logical expression. 977 */ 978 public function getIsNotNullExpression($expression) 979 { 980 return $expression . ' IS NOT NULL'; 981 } 982 983 /** 984 * Returns the SQL that checks if an expression evaluates to a value between two values. 985 * 986 * The parameter $expression is checked if it is between $value1 and $value2. 987 * 988 * Note: There is a slight difference in the way BETWEEN works on some databases. 989 * http://www.w3schools.com/sql/sql_between.asp. If you want complete database 990 * independence you should avoid using between(). 991 * 992 * @param string $expression The value to compare to. 993 * @param string $value1 The lower value to compare with. 994 * @param string $value2 The higher value to compare with. 995 * 996 * @return string The logical expression. 997 */ 998 public function getBetweenExpression($expression, $value1, $value2) 999 { 1000 return $expression . ' BETWEEN ' . $value1 . ' AND ' . $value2; 1001 } 1002 1003 /** 1004 * Returns the SQL to get the arccosine of a value. 1005 * 1006 * @param string $value 1007 * 1008 * @return string 1009 */ 1010 public function getAcosExpression($value) 1011 { 1012 return 'ACOS(' . $value . ')'; 1013 } 1014 1015 /** 1016 * Returns the SQL to get the sine of a value. 1017 * 1018 * @param string $value 1019 * 1020 * @return string 1021 */ 1022 public function getSinExpression($value) 1023 { 1024 return 'SIN(' . $value . ')'; 1025 } 1026 1027 /** 1028 * Returns the SQL to get the PI value. 1029 * 1030 * @return string 1031 */ 1032 public function getPiExpression() 1033 { 1034 return 'PI()'; 1035 } 1036 1037 /** 1038 * Returns the SQL to get the cosine of a value. 1039 * 1040 * @param string $value 1041 * 1042 * @return string 1043 */ 1044 public function getCosExpression($value) 1045 { 1046 return 'COS(' . $value . ')'; 1047 } 1048 1049 /** 1050 * Returns the SQL to calculate the difference in days between the two passed dates. 1051 * 1052 * Computes diff = date1 - date2. 1053 * 1054 * @param string $date1 1055 * @param string $date2 1056 * 1057 * @return string 1058 * 1059 * @throws DBALException If not supported on this platform. 1060 */ 1061 public function getDateDiffExpression($date1, $date2) 1062 { 1063 throw DBALException::notSupported(__METHOD__); 1064 } 1065 1066 /** 1067 * Returns the SQL to add the number of given seconds to a date. 1068 * 1069 * @param string $date 1070 * @param int $seconds 1071 * 1072 * @return string 1073 * 1074 * @throws DBALException If not supported on this platform. 1075 */ 1076 public function getDateAddSecondsExpression($date, $seconds) 1077 { 1078 return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, DateIntervalUnit::SECOND); 1079 } 1080 1081 /** 1082 * Returns the SQL to subtract the number of given seconds from a date. 1083 * 1084 * @param string $date 1085 * @param int $seconds 1086 * 1087 * @return string 1088 * 1089 * @throws DBALException If not supported on this platform. 1090 */ 1091 public function getDateSubSecondsExpression($date, $seconds) 1092 { 1093 return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, DateIntervalUnit::SECOND); 1094 } 1095 1096 /** 1097 * Returns the SQL to add the number of given minutes to a date. 1098 * 1099 * @param string $date 1100 * @param int $minutes 1101 * 1102 * @return string 1103 * 1104 * @throws DBALException If not supported on this platform. 1105 */ 1106 public function getDateAddMinutesExpression($date, $minutes) 1107 { 1108 return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, DateIntervalUnit::MINUTE); 1109 } 1110 1111 /** 1112 * Returns the SQL to subtract the number of given minutes from a date. 1113 * 1114 * @param string $date 1115 * @param int $minutes 1116 * 1117 * @return string 1118 * 1119 * @throws DBALException If not supported on this platform. 1120 */ 1121 public function getDateSubMinutesExpression($date, $minutes) 1122 { 1123 return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, DateIntervalUnit::MINUTE); 1124 } 1125 1126 /** 1127 * Returns the SQL to add the number of given hours to a date. 1128 * 1129 * @param string $date 1130 * @param int $hours 1131 * 1132 * @return string 1133 * 1134 * @throws DBALException If not supported on this platform. 1135 */ 1136 public function getDateAddHourExpression($date, $hours) 1137 { 1138 return $this->getDateArithmeticIntervalExpression($date, '+', $hours, DateIntervalUnit::HOUR); 1139 } 1140 1141 /** 1142 * Returns the SQL to subtract the number of given hours to a date. 1143 * 1144 * @param string $date 1145 * @param int $hours 1146 * 1147 * @return string 1148 * 1149 * @throws DBALException If not supported on this platform. 1150 */ 1151 public function getDateSubHourExpression($date, $hours) 1152 { 1153 return $this->getDateArithmeticIntervalExpression($date, '-', $hours, DateIntervalUnit::HOUR); 1154 } 1155 1156 /** 1157 * Returns the SQL to add the number of given days to a date. 1158 * 1159 * @param string $date 1160 * @param int $days 1161 * 1162 * @return string 1163 * 1164 * @throws DBALException If not supported on this platform. 1165 */ 1166 public function getDateAddDaysExpression($date, $days) 1167 { 1168 return $this->getDateArithmeticIntervalExpression($date, '+', $days, DateIntervalUnit::DAY); 1169 } 1170 1171 /** 1172 * Returns the SQL to subtract the number of given days to a date. 1173 * 1174 * @param string $date 1175 * @param int $days 1176 * 1177 * @return string 1178 * 1179 * @throws DBALException If not supported on this platform. 1180 */ 1181 public function getDateSubDaysExpression($date, $days) 1182 { 1183 return $this->getDateArithmeticIntervalExpression($date, '-', $days, DateIntervalUnit::DAY); 1184 } 1185 1186 /** 1187 * Returns the SQL to add the number of given weeks to a date. 1188 * 1189 * @param string $date 1190 * @param int $weeks 1191 * 1192 * @return string 1193 * 1194 * @throws DBALException If not supported on this platform. 1195 */ 1196 public function getDateAddWeeksExpression($date, $weeks) 1197 { 1198 return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, DateIntervalUnit::WEEK); 1199 } 1200 1201 /** 1202 * Returns the SQL to subtract the number of given weeks from a date. 1203 * 1204 * @param string $date 1205 * @param int $weeks 1206 * 1207 * @return string 1208 * 1209 * @throws DBALException If not supported on this platform. 1210 */ 1211 public function getDateSubWeeksExpression($date, $weeks) 1212 { 1213 return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, DateIntervalUnit::WEEK); 1214 } 1215 1216 /** 1217 * Returns the SQL to add the number of given months to a date. 1218 * 1219 * @param string $date 1220 * @param int $months 1221 * 1222 * @return string 1223 * 1224 * @throws DBALException If not supported on this platform. 1225 */ 1226 public function getDateAddMonthExpression($date, $months) 1227 { 1228 return $this->getDateArithmeticIntervalExpression($date, '+', $months, DateIntervalUnit::MONTH); 1229 } 1230 1231 /** 1232 * Returns the SQL to subtract the number of given months to a date. 1233 * 1234 * @param string $date 1235 * @param int $months 1236 * 1237 * @return string 1238 * 1239 * @throws DBALException If not supported on this platform. 1240 */ 1241 public function getDateSubMonthExpression($date, $months) 1242 { 1243 return $this->getDateArithmeticIntervalExpression($date, '-', $months, DateIntervalUnit::MONTH); 1244 } 1245 1246 /** 1247 * Returns the SQL to add the number of given quarters to a date. 1248 * 1249 * @param string $date 1250 * @param int $quarters 1251 * 1252 * @return string 1253 * 1254 * @throws DBALException If not supported on this platform. 1255 */ 1256 public function getDateAddQuartersExpression($date, $quarters) 1257 { 1258 return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, DateIntervalUnit::QUARTER); 1259 } 1260 1261 /** 1262 * Returns the SQL to subtract the number of given quarters from a date. 1263 * 1264 * @param string $date 1265 * @param int $quarters 1266 * 1267 * @return string 1268 * 1269 * @throws DBALException If not supported on this platform. 1270 */ 1271 public function getDateSubQuartersExpression($date, $quarters) 1272 { 1273 return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, DateIntervalUnit::QUARTER); 1274 } 1275 1276 /** 1277 * Returns the SQL to add the number of given years to a date. 1278 * 1279 * @param string $date 1280 * @param int $years 1281 * 1282 * @return string 1283 * 1284 * @throws DBALException If not supported on this platform. 1285 */ 1286 public function getDateAddYearsExpression($date, $years) 1287 { 1288 return $this->getDateArithmeticIntervalExpression($date, '+', $years, DateIntervalUnit::YEAR); 1289 } 1290 1291 /** 1292 * Returns the SQL to subtract the number of given years from a date. 1293 * 1294 * @param string $date 1295 * @param int $years 1296 * 1297 * @return string 1298 * 1299 * @throws DBALException If not supported on this platform. 1300 */ 1301 public function getDateSubYearsExpression($date, $years) 1302 { 1303 return $this->getDateArithmeticIntervalExpression($date, '-', $years, DateIntervalUnit::YEAR); 1304 } 1305 1306 /** 1307 * Returns the SQL for a date arithmetic expression. 1308 * 1309 * @param string $date The column or literal representing a date to perform the arithmetic operation on. 1310 * @param string $operator The arithmetic operator (+ or -). 1311 * @param int $interval The interval that shall be calculated into the date. 1312 * @param string $unit The unit of the interval that shall be calculated into the date. 1313 * One of the DATE_INTERVAL_UNIT_* constants. 1314 * 1315 * @return string 1316 * 1317 * @throws DBALException If not supported on this platform. 1318 */ 1319 protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit) 1320 { 1321 throw DBALException::notSupported(__METHOD__); 1322 } 1323 1324 /** 1325 * Returns the SQL bit AND comparison expression. 1326 * 1327 * @param string $value1 1328 * @param string $value2 1329 * 1330 * @return string 1331 */ 1332 public function getBitAndComparisonExpression($value1, $value2) 1333 { 1334 return '(' . $value1 . ' & ' . $value2 . ')'; 1335 } 1336 1337 /** 1338 * Returns the SQL bit OR comparison expression. 1339 * 1340 * @param string $value1 1341 * @param string $value2 1342 * 1343 * @return string 1344 */ 1345 public function getBitOrComparisonExpression($value1, $value2) 1346 { 1347 return '(' . $value1 . ' | ' . $value2 . ')'; 1348 } 1349 1350 /** 1351 * Returns the FOR UPDATE expression. 1352 * 1353 * @return string 1354 */ 1355 public function getForUpdateSQL() 1356 { 1357 return 'FOR UPDATE'; 1358 } 1359 1360 /** 1361 * Honors that some SQL vendors such as MsSql use table hints for locking instead of the 1362 * ANSI SQL FOR UPDATE specification. 1363 * 1364 * @param string $fromClause The FROM clause to append the hint for the given lock mode to. 1365 * @param int|null $lockMode One of the Doctrine\DBAL\LockMode::* constants. If null is given, nothing will 1366 * be appended to the FROM clause. 1367 * 1368 * @return string 1369 */ 1370 public function appendLockHint($fromClause, $lockMode) 1371 { 1372 return $fromClause; 1373 } 1374 1375 /** 1376 * Returns the SQL snippet to append to any SELECT statement which locks rows in shared read lock. 1377 * 1378 * This defaults to the ANSI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database 1379 * vendors allow to lighten this constraint up to be a real read lock. 1380 * 1381 * @return string 1382 */ 1383 public function getReadLockSQL() 1384 { 1385 return $this->getForUpdateSQL(); 1386 } 1387 1388 /** 1389 * Returns the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows. 1390 * 1391 * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ANSI SQL standard. 1392 * 1393 * @return string 1394 */ 1395 public function getWriteLockSQL() 1396 { 1397 return $this->getForUpdateSQL(); 1398 } 1399 1400 /** 1401 * Returns the SQL snippet to drop an existing database. 1402 * 1403 * @param string $database The name of the database that should be dropped. 1404 * 1405 * @return string 1406 */ 1407 public function getDropDatabaseSQL($database) 1408 { 1409 return 'DROP DATABASE ' . $database; 1410 } 1411 1412 /** 1413 * Returns the SQL snippet to drop an existing table. 1414 * 1415 * @param Table|string $table 1416 * 1417 * @return string 1418 * 1419 * @throws InvalidArgumentException 1420 */ 1421 public function getDropTableSQL($table) 1422 { 1423 $tableArg = $table; 1424 1425 if ($table instanceof Table) { 1426 $table = $table->getQuotedName($this); 1427 } 1428 1429 if (! is_string($table)) { 1430 throw new InvalidArgumentException( 1431 __METHOD__ . '() expects $table parameter to be string or ' . Table::class . '.' 1432 ); 1433 } 1434 1435 if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaDropTable)) { 1436 $eventArgs = new SchemaDropTableEventArgs($tableArg, $this); 1437 $this->_eventManager->dispatchEvent(Events::onSchemaDropTable, $eventArgs); 1438 1439 if ($eventArgs->isDefaultPrevented()) { 1440 $sql = $eventArgs->getSql(); 1441 1442 if ($sql === null) { 1443 throw new UnexpectedValueException('Default implementation of DROP TABLE was overridden with NULL'); 1444 } 1445 1446 return $sql; 1447 } 1448 } 1449 1450 return 'DROP TABLE ' . $table; 1451 } 1452 1453 /** 1454 * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction. 1455 * 1456 * @param Table|string $table 1457 * 1458 * @return string 1459 */ 1460 public function getDropTemporaryTableSQL($table) 1461 { 1462 return $this->getDropTableSQL($table); 1463 } 1464 1465 /** 1466 * Returns the SQL to drop an index from a table. 1467 * 1468 * @param Index|string $index 1469 * @param Table|string $table 1470 * 1471 * @return string 1472 * 1473 * @throws InvalidArgumentException 1474 */ 1475 public function getDropIndexSQL($index, $table = null) 1476 { 1477 if ($index instanceof Index) { 1478 $index = $index->getQuotedName($this); 1479 } elseif (! is_string($index)) { 1480 throw new InvalidArgumentException( 1481 __METHOD__ . '() expects $index parameter to be string or ' . Index::class . '.' 1482 ); 1483 } 1484 1485 return 'DROP INDEX ' . $index; 1486 } 1487 1488 /** 1489 * Returns the SQL to drop a constraint. 1490 * 1491 * @param Constraint|string $constraint 1492 * @param Table|string $table 1493 * 1494 * @return string 1495 */ 1496 public function getDropConstraintSQL($constraint, $table) 1497 { 1498 if (! $constraint instanceof Constraint) { 1499 $constraint = new Identifier($constraint); 1500 } 1501 1502 if (! $table instanceof Table) { 1503 $table = new Identifier($table); 1504 } 1505 1506 $constraint = $constraint->getQuotedName($this); 1507 $table = $table->getQuotedName($this); 1508 1509 return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint; 1510 } 1511 1512 /** 1513 * Returns the SQL to drop a foreign key. 1514 * 1515 * @param ForeignKeyConstraint|string $foreignKey 1516 * @param Table|string $table 1517 * 1518 * @return string 1519 */ 1520 public function getDropForeignKeySQL($foreignKey, $table) 1521 { 1522 if (! $foreignKey instanceof ForeignKeyConstraint) { 1523 $foreignKey = new Identifier($foreignKey); 1524 } 1525 1526 if (! $table instanceof Table) { 1527 $table = new Identifier($table); 1528 } 1529 1530 $foreignKey = $foreignKey->getQuotedName($this); 1531 $table = $table->getQuotedName($this); 1532 1533 return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey; 1534 } 1535 1536 /** 1537 * Returns the SQL statement(s) to create a table with the specified name, columns and constraints 1538 * on this platform. 1539 * 1540 * @param int $createFlags 1541 * 1542 * @return string[] The sequence of SQL statements. 1543 * 1544 * @throws DBALException 1545 * @throws InvalidArgumentException 1546 */ 1547 public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES) 1548 { 1549 if (! is_int($createFlags)) { 1550 throw new InvalidArgumentException( 1551 'Second argument of AbstractPlatform::getCreateTableSQL() has to be integer.' 1552 ); 1553 } 1554 1555 if (count($table->getColumns()) === 0) { 1556 throw DBALException::noColumnsSpecifiedForTable($table->getName()); 1557 } 1558 1559 $tableName = $table->getQuotedName($this); 1560 $options = $table->getOptions(); 1561 $options['uniqueConstraints'] = []; 1562 $options['indexes'] = []; 1563 $options['primary'] = []; 1564 1565 if (($createFlags & self::CREATE_INDEXES) > 0) { 1566 foreach ($table->getIndexes() as $index) { 1567 if ($index->isPrimary()) { 1568 $options['primary'] = $index->getQuotedColumns($this); 1569 $options['primary_index'] = $index; 1570 } else { 1571 $options['indexes'][$index->getQuotedName($this)] = $index; 1572 } 1573 } 1574 } 1575 1576 $columnSql = []; 1577 $columns = []; 1578 1579 foreach ($table->getColumns() as $column) { 1580 if ( 1581 $this->_eventManager !== null 1582 && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn) 1583 ) { 1584 $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this); 1585 $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs); 1586 1587 $columnSql = array_merge($columnSql, $eventArgs->getSql()); 1588 1589 if ($eventArgs->isDefaultPrevented()) { 1590 continue; 1591 } 1592 } 1593 1594 $columnData = array_merge($column->toArray(), [ 1595 'name' => $column->getQuotedName($this), 1596 'version' => $column->hasPlatformOption('version') ? $column->getPlatformOption('version') : false, 1597 'comment' => $this->getColumnComment($column), 1598 ]); 1599 1600 if ($columnData['type'] instanceof Types\StringType && $columnData['length'] === null) { 1601 $columnData['length'] = 255; 1602 } 1603 1604 if (in_array($column->getName(), $options['primary'])) { 1605 $columnData['primary'] = true; 1606 } 1607 1608 $columns[$columnData['name']] = $columnData; 1609 } 1610 1611 if (($createFlags & self::CREATE_FOREIGNKEYS) > 0) { 1612 $options['foreignKeys'] = []; 1613 foreach ($table->getForeignKeys() as $fkConstraint) { 1614 $options['foreignKeys'][] = $fkConstraint; 1615 } 1616 } 1617 1618 if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) { 1619 $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this); 1620 $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs); 1621 1622 if ($eventArgs->isDefaultPrevented()) { 1623 return array_merge($eventArgs->getSql(), $columnSql); 1624 } 1625 } 1626 1627 $sql = $this->_getCreateTableSQL($tableName, $columns, $options); 1628 if ($this->supportsCommentOnStatement()) { 1629 if ($table->hasOption('comment')) { 1630 $sql[] = $this->getCommentOnTableSQL($tableName, $table->getOption('comment')); 1631 } 1632 1633 foreach ($table->getColumns() as $column) { 1634 $comment = $this->getColumnComment($column); 1635 1636 if ($comment === null || $comment === '') { 1637 continue; 1638 } 1639 1640 $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment); 1641 } 1642 } 1643 1644 return array_merge($sql, $columnSql); 1645 } 1646 1647 protected function getCommentOnTableSQL(string $tableName, ?string $comment): string 1648 { 1649 $tableName = new Identifier($tableName); 1650 1651 return sprintf( 1652 'COMMENT ON TABLE %s IS %s', 1653 $tableName->getQuotedName($this), 1654 $this->quoteStringLiteral((string) $comment) 1655 ); 1656 } 1657 1658 /** 1659 * @param string $tableName 1660 * @param string $columnName 1661 * @param string|null $comment 1662 * 1663 * @return string 1664 */ 1665 public function getCommentOnColumnSQL($tableName, $columnName, $comment) 1666 { 1667 $tableName = new Identifier($tableName); 1668 $columnName = new Identifier($columnName); 1669 1670 return sprintf( 1671 'COMMENT ON COLUMN %s.%s IS %s', 1672 $tableName->getQuotedName($this), 1673 $columnName->getQuotedName($this), 1674 $this->quoteStringLiteral((string) $comment) 1675 ); 1676 } 1677 1678 /** 1679 * Returns the SQL to create inline comment on a column. 1680 * 1681 * @param string $comment 1682 * 1683 * @return string 1684 * 1685 * @throws DBALException If not supported on this platform. 1686 */ 1687 public function getInlineColumnCommentSQL($comment) 1688 { 1689 if (! $this->supportsInlineColumnComments()) { 1690 throw DBALException::notSupported(__METHOD__); 1691 } 1692 1693 return 'COMMENT ' . $this->quoteStringLiteral($comment); 1694 } 1695 1696 /** 1697 * Returns the SQL used to create a table. 1698 * 1699 * @param string $name 1700 * @param mixed[][] $columns 1701 * @param mixed[] $options 1702 * 1703 * @return string[] 1704 */ 1705 protected function _getCreateTableSQL($name, array $columns, array $options = []) 1706 { 1707 $columnListSql = $this->getColumnDeclarationListSQL($columns); 1708 1709 if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { 1710 foreach ($options['uniqueConstraints'] as $index => $definition) { 1711 $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); 1712 } 1713 } 1714 1715 if (isset($options['primary']) && ! empty($options['primary'])) { 1716 $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; 1717 } 1718 1719 if (isset($options['indexes']) && ! empty($options['indexes'])) { 1720 foreach ($options['indexes'] as $index => $definition) { 1721 $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition); 1722 } 1723 } 1724 1725 $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; 1726 1727 $check = $this->getCheckDeclarationSQL($columns); 1728 if (! empty($check)) { 1729 $query .= ', ' . $check; 1730 } 1731 1732 $query .= ')'; 1733 1734 $sql[] = $query; 1735 1736 if (isset($options['foreignKeys'])) { 1737 foreach ((array) $options['foreignKeys'] as $definition) { 1738 $sql[] = $this->getCreateForeignKeySQL($definition, $name); 1739 } 1740 } 1741 1742 return $sql; 1743 } 1744 1745 /** 1746 * @return string 1747 */ 1748 public function getCreateTemporaryTableSnippetSQL() 1749 { 1750 return 'CREATE TEMPORARY TABLE'; 1751 } 1752 1753 /** 1754 * Returns the SQL to create a sequence on this platform. 1755 * 1756 * @return string 1757 * 1758 * @throws DBALException If not supported on this platform. 1759 */ 1760 public function getCreateSequenceSQL(Sequence $sequence) 1761 { 1762 throw DBALException::notSupported(__METHOD__); 1763 } 1764 1765 /** 1766 * Returns the SQL to change a sequence on this platform. 1767 * 1768 * @return string 1769 * 1770 * @throws DBALException If not supported on this platform. 1771 */ 1772 public function getAlterSequenceSQL(Sequence $sequence) 1773 { 1774 throw DBALException::notSupported(__METHOD__); 1775 } 1776 1777 /** 1778 * Returns the SQL to create a constraint on a table on this platform. 1779 * 1780 * @param Table|string $table 1781 * 1782 * @return string 1783 * 1784 * @throws InvalidArgumentException 1785 */ 1786 public function getCreateConstraintSQL(Constraint $constraint, $table) 1787 { 1788 if ($table instanceof Table) { 1789 $table = $table->getQuotedName($this); 1790 } 1791 1792 $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this); 1793 1794 $columnList = '(' . implode(', ', $constraint->getQuotedColumns($this)) . ')'; 1795 1796 $referencesClause = ''; 1797 if ($constraint instanceof Index) { 1798 if ($constraint->isPrimary()) { 1799 $query .= ' PRIMARY KEY'; 1800 } elseif ($constraint->isUnique()) { 1801 $query .= ' UNIQUE'; 1802 } else { 1803 throw new InvalidArgumentException( 1804 'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().' 1805 ); 1806 } 1807 } elseif ($constraint instanceof ForeignKeyConstraint) { 1808 $query .= ' FOREIGN KEY'; 1809 1810 $referencesClause = ' REFERENCES ' . $constraint->getQuotedForeignTableName($this) . 1811 ' (' . implode(', ', $constraint->getQuotedForeignColumns($this)) . ')'; 1812 } 1813 1814 $query .= ' ' . $columnList . $referencesClause; 1815 1816 return $query; 1817 } 1818 1819 /** 1820 * Returns the SQL to create an index on a table on this platform. 1821 * 1822 * @param Table|string $table The name of the table on which the index is to be created. 1823 * 1824 * @return string 1825 * 1826 * @throws InvalidArgumentException 1827 */ 1828 public function getCreateIndexSQL(Index $index, $table) 1829 { 1830 if ($table instanceof Table) { 1831 $table = $table->getQuotedName($this); 1832 } 1833 1834 $name = $index->getQuotedName($this); 1835 $columns = $index->getColumns(); 1836 1837 if (count($columns) === 0) { 1838 throw new InvalidArgumentException("Incomplete definition. 'columns' required."); 1839 } 1840 1841 if ($index->isPrimary()) { 1842 return $this->getCreatePrimaryKeySQL($index, $table); 1843 } 1844 1845 $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; 1846 $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index); 1847 1848 return $query; 1849 } 1850 1851 /** 1852 * Adds condition for partial index. 1853 * 1854 * @return string 1855 */ 1856 protected function getPartialIndexSQL(Index $index) 1857 { 1858 if ($this->supportsPartialIndexes() && $index->hasOption('where')) { 1859 return ' WHERE ' . $index->getOption('where'); 1860 } 1861 1862 return ''; 1863 } 1864 1865 /** 1866 * Adds additional flags for index generation. 1867 * 1868 * @return string 1869 */ 1870 protected function getCreateIndexSQLFlags(Index $index) 1871 { 1872 return $index->isUnique() ? 'UNIQUE ' : ''; 1873 } 1874 1875 /** 1876 * Returns the SQL to create an unnamed primary key constraint. 1877 * 1878 * @param Table|string $table 1879 * 1880 * @return string 1881 */ 1882 public function getCreatePrimaryKeySQL(Index $index, $table) 1883 { 1884 if ($table instanceof Table) { 1885 $table = $table->getQuotedName($this); 1886 } 1887 1888 return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')'; 1889 } 1890 1891 /** 1892 * Returns the SQL to create a named schema. 1893 * 1894 * @param string $schemaName 1895 * 1896 * @return string 1897 * 1898 * @throws DBALException If not supported on this platform. 1899 */ 1900 public function getCreateSchemaSQL($schemaName) 1901 { 1902 throw DBALException::notSupported(__METHOD__); 1903 } 1904 1905 /** 1906 * Quotes a string so that it can be safely used as a table or column name, 1907 * even if it is a reserved word of the platform. This also detects identifier 1908 * chains separated by dot and quotes them independently. 1909 * 1910 * NOTE: Just because you CAN use quoted identifiers doesn't mean 1911 * you SHOULD use them. In general, they end up causing way more 1912 * problems than they solve. 1913 * 1914 * @param string $str The identifier name to be quoted. 1915 * 1916 * @return string The quoted identifier string. 1917 */ 1918 public function quoteIdentifier($str) 1919 { 1920 if (strpos($str, '.') !== false) { 1921 $parts = array_map([$this, 'quoteSingleIdentifier'], explode('.', $str)); 1922 1923 return implode('.', $parts); 1924 } 1925 1926 return $this->quoteSingleIdentifier($str); 1927 } 1928 1929 /** 1930 * Quotes a single identifier (no dot chain separation). 1931 * 1932 * @param string $str The identifier name to be quoted. 1933 * 1934 * @return string The quoted identifier string. 1935 */ 1936 public function quoteSingleIdentifier($str) 1937 { 1938 $c = $this->getIdentifierQuoteCharacter(); 1939 1940 return $c . str_replace($c, $c . $c, $str) . $c; 1941 } 1942 1943 /** 1944 * Returns the SQL to create a new foreign key. 1945 * 1946 * @param ForeignKeyConstraint $foreignKey The foreign key constraint. 1947 * @param Table|string $table The name of the table on which the foreign key is to be created. 1948 * 1949 * @return string 1950 */ 1951 public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) 1952 { 1953 if ($table instanceof Table) { 1954 $table = $table->getQuotedName($this); 1955 } 1956 1957 return 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey); 1958 } 1959 1960 /** 1961 * Gets the SQL statements for altering an existing table. 1962 * 1963 * This method returns an array of SQL statements, since some platforms need several statements. 1964 * 1965 * @return string[] 1966 * 1967 * @throws DBALException If not supported on this platform. 1968 */ 1969 public function getAlterTableSQL(TableDiff $diff) 1970 { 1971 throw DBALException::notSupported(__METHOD__); 1972 } 1973 1974 /** 1975 * @param mixed[] $columnSql 1976 * 1977 * @return bool 1978 */ 1979 protected function onSchemaAlterTableAddColumn(Column $column, TableDiff $diff, &$columnSql) 1980 { 1981 if ($this->_eventManager === null) { 1982 return false; 1983 } 1984 1985 if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableAddColumn)) { 1986 return false; 1987 } 1988 1989 $eventArgs = new SchemaAlterTableAddColumnEventArgs($column, $diff, $this); 1990 $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableAddColumn, $eventArgs); 1991 1992 $columnSql = array_merge($columnSql, $eventArgs->getSql()); 1993 1994 return $eventArgs->isDefaultPrevented(); 1995 } 1996 1997 /** 1998 * @param string[] $columnSql 1999 * 2000 * @return bool 2001 */ 2002 protected function onSchemaAlterTableRemoveColumn(Column $column, TableDiff $diff, &$columnSql) 2003 { 2004 if ($this->_eventManager === null) { 2005 return false; 2006 } 2007 2008 if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRemoveColumn)) { 2009 return false; 2010 } 2011 2012 $eventArgs = new SchemaAlterTableRemoveColumnEventArgs($column, $diff, $this); 2013 $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRemoveColumn, $eventArgs); 2014 2015 $columnSql = array_merge($columnSql, $eventArgs->getSql()); 2016 2017 return $eventArgs->isDefaultPrevented(); 2018 } 2019 2020 /** 2021 * @param string[] $columnSql 2022 * 2023 * @return bool 2024 */ 2025 protected function onSchemaAlterTableChangeColumn(ColumnDiff $columnDiff, TableDiff $diff, &$columnSql) 2026 { 2027 if ($this->_eventManager === null) { 2028 return false; 2029 } 2030 2031 if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) { 2032 return false; 2033 } 2034 2035 $eventArgs = new SchemaAlterTableChangeColumnEventArgs($columnDiff, $diff, $this); 2036 $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableChangeColumn, $eventArgs); 2037 2038 $columnSql = array_merge($columnSql, $eventArgs->getSql()); 2039 2040 return $eventArgs->isDefaultPrevented(); 2041 } 2042 2043 /** 2044 * @param string $oldColumnName 2045 * @param string[] $columnSql 2046 * 2047 * @return bool 2048 */ 2049 protected function onSchemaAlterTableRenameColumn($oldColumnName, Column $column, TableDiff $diff, &$columnSql) 2050 { 2051 if ($this->_eventManager === null) { 2052 return false; 2053 } 2054 2055 if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRenameColumn)) { 2056 return false; 2057 } 2058 2059 $eventArgs = new SchemaAlterTableRenameColumnEventArgs($oldColumnName, $column, $diff, $this); 2060 $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRenameColumn, $eventArgs); 2061 2062 $columnSql = array_merge($columnSql, $eventArgs->getSql()); 2063 2064 return $eventArgs->isDefaultPrevented(); 2065 } 2066 2067 /** 2068 * @param string[] $sql 2069 * 2070 * @return bool 2071 */ 2072 protected function onSchemaAlterTable(TableDiff $diff, &$sql) 2073 { 2074 if ($this->_eventManager === null) { 2075 return false; 2076 } 2077 2078 if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTable)) { 2079 return false; 2080 } 2081 2082 $eventArgs = new SchemaAlterTableEventArgs($diff, $this); 2083 $this->_eventManager->dispatchEvent(Events::onSchemaAlterTable, $eventArgs); 2084 2085 $sql = array_merge($sql, $eventArgs->getSql()); 2086 2087 return $eventArgs->isDefaultPrevented(); 2088 } 2089 2090 /** 2091 * @return string[] 2092 */ 2093 protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) 2094 { 2095 $tableName = $diff->getName($this)->getQuotedName($this); 2096 2097 $sql = []; 2098 if ($this->supportsForeignKeyConstraints()) { 2099 foreach ($diff->removedForeignKeys as $foreignKey) { 2100 $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); 2101 } 2102 2103 foreach ($diff->changedForeignKeys as $foreignKey) { 2104 $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); 2105 } 2106 } 2107 2108 foreach ($diff->removedIndexes as $index) { 2109 $sql[] = $this->getDropIndexSQL($index, $tableName); 2110 } 2111 2112 foreach ($diff->changedIndexes as $index) { 2113 $sql[] = $this->getDropIndexSQL($index, $tableName); 2114 } 2115 2116 return $sql; 2117 } 2118 2119 /** 2120 * @return string[] 2121 */ 2122 protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) 2123 { 2124 $sql = []; 2125 $newName = $diff->getNewName(); 2126 2127 if ($newName !== false) { 2128 $tableName = $newName->getQuotedName($this); 2129 } else { 2130 $tableName = $diff->getName($this)->getQuotedName($this); 2131 } 2132 2133 if ($this->supportsForeignKeyConstraints()) { 2134 foreach ($diff->addedForeignKeys as $foreignKey) { 2135 $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); 2136 } 2137 2138 foreach ($diff->changedForeignKeys as $foreignKey) { 2139 $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); 2140 } 2141 } 2142 2143 foreach ($diff->addedIndexes as $index) { 2144 $sql[] = $this->getCreateIndexSQL($index, $tableName); 2145 } 2146 2147 foreach ($diff->changedIndexes as $index) { 2148 $sql[] = $this->getCreateIndexSQL($index, $tableName); 2149 } 2150 2151 foreach ($diff->renamedIndexes as $oldIndexName => $index) { 2152 $oldIndexName = new Identifier($oldIndexName); 2153 $sql = array_merge( 2154 $sql, 2155 $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableName) 2156 ); 2157 } 2158 2159 return $sql; 2160 } 2161 2162 /** 2163 * Returns the SQL for renaming an index on a table. 2164 * 2165 * @param string $oldIndexName The name of the index to rename from. 2166 * @param Index $index The definition of the index to rename to. 2167 * @param string $tableName The table to rename the given index on. 2168 * 2169 * @return string[] The sequence of SQL statements for renaming the given index. 2170 */ 2171 protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName) 2172 { 2173 return [ 2174 $this->getDropIndexSQL($oldIndexName, $tableName), 2175 $this->getCreateIndexSQL($index, $tableName), 2176 ]; 2177 } 2178 2179 /** 2180 * Common code for alter table statement generation that updates the changed Index and Foreign Key definitions. 2181 * 2182 * @deprecated 2183 * 2184 * @return string[] 2185 */ 2186 protected function _getAlterTableIndexForeignKeySQL(TableDiff $diff) 2187 { 2188 return array_merge( 2189 $this->getPreAlterTableIndexForeignKeySQL($diff), 2190 $this->getPostAlterTableIndexForeignKeySQL($diff) 2191 ); 2192 } 2193 2194 /** 2195 * Gets declaration of a number of columns in bulk. 2196 * 2197 * @param mixed[][] $columns A multidimensional associative array. 2198 * The first dimension determines the column name, while the second 2199 * dimension is keyed with the name of the properties 2200 * of the column being declared as array indexes. Currently, the types 2201 * of supported column properties are as follows: 2202 * 2203 * length 2204 * Integer value that determines the maximum length of the text 2205 * column. If this argument is missing the column should be 2206 * declared to have the longest length allowed by the DBMS. 2207 * 2208 * default 2209 * Text value to be used as default for this column. 2210 * 2211 * notnull 2212 * Boolean flag that indicates whether this column is constrained 2213 * to not be set to null. 2214 * charset 2215 * Text value with the default CHARACTER SET for this column. 2216 * collation 2217 * Text value with the default COLLATION for this column. 2218 * unique 2219 * unique constraint 2220 * 2221 * @return string 2222 */ 2223 public function getColumnDeclarationListSQL(array $columns) 2224 { 2225 $declarations = []; 2226 2227 foreach ($columns as $name => $column) { 2228 $declarations[] = $this->getColumnDeclarationSQL($name, $column); 2229 } 2230 2231 return implode(', ', $declarations); 2232 } 2233 2234 /** 2235 * Obtains DBMS specific SQL code portion needed to declare a generic type 2236 * column to be used in statements like CREATE TABLE. 2237 * 2238 * @param string $name The name the column to be declared. 2239 * @param mixed[] $column An associative array with the name of the properties 2240 * of the column being declared as array indexes. Currently, the types 2241 * of supported column properties are as follows: 2242 * 2243 * length 2244 * Integer value that determines the maximum length of the text 2245 * column. If this argument is missing the column should be 2246 * declared to have the longest length allowed by the DBMS. 2247 * 2248 * default 2249 * Text value to be used as default for this column. 2250 * 2251 * notnull 2252 * Boolean flag that indicates whether this column is constrained 2253 * to not be set to null. 2254 * charset 2255 * Text value with the default CHARACTER SET for this column. 2256 * collation 2257 * Text value with the default COLLATION for this column. 2258 * unique 2259 * unique constraint 2260 * check 2261 * column check constraint 2262 * columnDefinition 2263 * a string that defines the complete column 2264 * 2265 * @return string DBMS specific SQL code portion that should be used to declare the column. 2266 */ 2267 public function getColumnDeclarationSQL($name, array $column) 2268 { 2269 if (isset($column['columnDefinition'])) { 2270 $declaration = $this->getCustomTypeDeclarationSQL($column); 2271 } else { 2272 $default = $this->getDefaultValueDeclarationSQL($column); 2273 2274 $charset = isset($column['charset']) && $column['charset'] ? 2275 ' ' . $this->getColumnCharsetDeclarationSQL($column['charset']) : ''; 2276 2277 $collation = isset($column['collation']) && $column['collation'] ? 2278 ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; 2279 2280 $notnull = isset($column['notnull']) && $column['notnull'] ? ' NOT NULL' : ''; 2281 2282 $unique = isset($column['unique']) && $column['unique'] ? 2283 ' ' . $this->getUniqueFieldDeclarationSQL() : ''; 2284 2285 $check = isset($column['check']) && $column['check'] ? 2286 ' ' . $column['check'] : ''; 2287 2288 $typeDecl = $column['type']->getSQLDeclaration($column, $this); 2289 $declaration = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation; 2290 2291 if ($this->supportsInlineColumnComments() && isset($column['comment']) && $column['comment'] !== '') { 2292 $declaration .= ' ' . $this->getInlineColumnCommentSQL($column['comment']); 2293 } 2294 } 2295 2296 return $name . ' ' . $declaration; 2297 } 2298 2299 /** 2300 * Returns the SQL snippet that declares a floating point column of arbitrary precision. 2301 * 2302 * @param mixed[] $column 2303 * 2304 * @return string 2305 */ 2306 public function getDecimalTypeDeclarationSQL(array $column) 2307 { 2308 $column['precision'] = ! isset($column['precision']) || empty($column['precision']) 2309 ? 10 : $column['precision']; 2310 $column['scale'] = ! isset($column['scale']) || empty($column['scale']) 2311 ? 0 : $column['scale']; 2312 2313 return 'NUMERIC(' . $column['precision'] . ', ' . $column['scale'] . ')'; 2314 } 2315 2316 /** 2317 * Obtains DBMS specific SQL code portion needed to set a default value 2318 * declaration to be used in statements like CREATE TABLE. 2319 * 2320 * @param mixed[] $column The column definition array. 2321 * 2322 * @return string DBMS specific SQL code portion needed to set a default value. 2323 */ 2324 public function getDefaultValueDeclarationSQL($column) 2325 { 2326 if (! isset($column['default'])) { 2327 return empty($column['notnull']) ? ' DEFAULT NULL' : ''; 2328 } 2329 2330 $default = $column['default']; 2331 2332 if (! isset($column['type'])) { 2333 return " DEFAULT '" . $default . "'"; 2334 } 2335 2336 $type = $column['type']; 2337 2338 if ($type instanceof Types\PhpIntegerMappingType) { 2339 return ' DEFAULT ' . $default; 2340 } 2341 2342 if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) { 2343 return ' DEFAULT ' . $this->getCurrentTimestampSQL(); 2344 } 2345 2346 if ($type instanceof Types\TimeType && $default === $this->getCurrentTimeSQL()) { 2347 return ' DEFAULT ' . $this->getCurrentTimeSQL(); 2348 } 2349 2350 if ($type instanceof Types\DateType && $default === $this->getCurrentDateSQL()) { 2351 return ' DEFAULT ' . $this->getCurrentDateSQL(); 2352 } 2353 2354 if ($type instanceof Types\BooleanType) { 2355 return " DEFAULT '" . $this->convertBooleans($default) . "'"; 2356 } 2357 2358 return ' DEFAULT ' . $this->quoteStringLiteral($default); 2359 } 2360 2361 /** 2362 * Obtains DBMS specific SQL code portion needed to set a CHECK constraint 2363 * declaration to be used in statements like CREATE TABLE. 2364 * 2365 * @param string[]|mixed[][] $definition The check definition. 2366 * 2367 * @return string DBMS specific SQL code portion needed to set a CHECK constraint. 2368 */ 2369 public function getCheckDeclarationSQL(array $definition) 2370 { 2371 $constraints = []; 2372 foreach ($definition as $column => $def) { 2373 if (is_string($def)) { 2374 $constraints[] = 'CHECK (' . $def . ')'; 2375 } else { 2376 if (isset($def['min'])) { 2377 $constraints[] = 'CHECK (' . $column . ' >= ' . $def['min'] . ')'; 2378 } 2379 2380 if (isset($def['max'])) { 2381 $constraints[] = 'CHECK (' . $column . ' <= ' . $def['max'] . ')'; 2382 } 2383 } 2384 } 2385 2386 return implode(', ', $constraints); 2387 } 2388 2389 /** 2390 * Obtains DBMS specific SQL code portion needed to set a unique 2391 * constraint declaration to be used in statements like CREATE TABLE. 2392 * 2393 * @param string $name The name of the unique constraint. 2394 * @param Index $index The index definition. 2395 * 2396 * @return string DBMS specific SQL code portion needed to set a constraint. 2397 * 2398 * @throws InvalidArgumentException 2399 */ 2400 public function getUniqueConstraintDeclarationSQL($name, Index $index) 2401 { 2402 $columns = $index->getColumns(); 2403 $name = new Identifier($name); 2404 2405 if (count($columns) === 0) { 2406 throw new InvalidArgumentException("Incomplete definition. 'columns' required."); 2407 } 2408 2409 return 'CONSTRAINT ' . $name->getQuotedName($this) . ' UNIQUE (' 2410 . $this->getIndexFieldDeclarationListSQL($index) 2411 . ')' . $this->getPartialIndexSQL($index); 2412 } 2413 2414 /** 2415 * Obtains DBMS specific SQL code portion needed to set an index 2416 * declaration to be used in statements like CREATE TABLE. 2417 * 2418 * @param string $name The name of the index. 2419 * @param Index $index The index definition. 2420 * 2421 * @return string DBMS specific SQL code portion needed to set an index. 2422 * 2423 * @throws InvalidArgumentException 2424 */ 2425 public function getIndexDeclarationSQL($name, Index $index) 2426 { 2427 $columns = $index->getColumns(); 2428 $name = new Identifier($name); 2429 2430 if (count($columns) === 0) { 2431 throw new InvalidArgumentException("Incomplete definition. 'columns' required."); 2432 } 2433 2434 return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name->getQuotedName($this) . ' (' 2435 . $this->getIndexFieldDeclarationListSQL($index) 2436 . ')' . $this->getPartialIndexSQL($index); 2437 } 2438 2439 /** 2440 * Obtains SQL code portion needed to create a custom column, 2441 * e.g. when a column has the "columnDefinition" keyword. 2442 * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate. 2443 * 2444 * @param mixed[] $column 2445 * 2446 * @return string 2447 */ 2448 public function getCustomTypeDeclarationSQL(array $column) 2449 { 2450 return $column['columnDefinition']; 2451 } 2452 2453 /** 2454 * Obtains DBMS specific SQL code portion needed to set an index 2455 * declaration to be used in statements like CREATE TABLE. 2456 * 2457 * @param mixed[]|Index $columnsOrIndex array declaration is deprecated, prefer passing Index to this method 2458 */ 2459 public function getIndexFieldDeclarationListSQL($columnsOrIndex): string 2460 { 2461 if ($columnsOrIndex instanceof Index) { 2462 return implode(', ', $columnsOrIndex->getQuotedColumns($this)); 2463 } 2464 2465 if (! is_array($columnsOrIndex)) { 2466 throw new InvalidArgumentException('Fields argument should be an Index or array.'); 2467 } 2468 2469 $ret = []; 2470 2471 foreach ($columnsOrIndex as $column => $definition) { 2472 if (is_array($definition)) { 2473 $ret[] = $column; 2474 } else { 2475 $ret[] = $definition; 2476 } 2477 } 2478 2479 return implode(', ', $ret); 2480 } 2481 2482 /** 2483 * Returns the required SQL string that fits between CREATE ... TABLE 2484 * to create the table as a temporary table. 2485 * 2486 * Should be overridden in driver classes to return the correct string for the 2487 * specific database type. 2488 * 2489 * The default is to return the string "TEMPORARY" - this will result in a 2490 * SQL error for any database that does not support temporary tables, or that 2491 * requires a different SQL command from "CREATE TEMPORARY TABLE". 2492 * 2493 * @return string The string required to be placed between "CREATE" and "TABLE" 2494 * to generate a temporary table, if possible. 2495 */ 2496 public function getTemporaryTableSQL() 2497 { 2498 return 'TEMPORARY'; 2499 } 2500 2501 /** 2502 * Some vendors require temporary table names to be qualified specially. 2503 * 2504 * @param string $tableName 2505 * 2506 * @return string 2507 */ 2508 public function getTemporaryTableName($tableName) 2509 { 2510 return $tableName; 2511 } 2512 2513 /** 2514 * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint 2515 * of a column declaration to be used in statements like CREATE TABLE. 2516 * 2517 * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint 2518 * of a column declaration. 2519 */ 2520 public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) 2521 { 2522 $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey); 2523 $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey); 2524 2525 return $sql; 2526 } 2527 2528 /** 2529 * Returns the FOREIGN KEY query section dealing with non-standard options 2530 * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... 2531 * 2532 * @param ForeignKeyConstraint $foreignKey The foreign key definition. 2533 * 2534 * @return string 2535 */ 2536 public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) 2537 { 2538 $query = ''; 2539 if ($this->supportsForeignKeyOnUpdate() && $foreignKey->hasOption('onUpdate')) { 2540 $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate')); 2541 } 2542 2543 if ($foreignKey->hasOption('onDelete')) { 2544 $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); 2545 } 2546 2547 return $query; 2548 } 2549 2550 /** 2551 * Returns the given referential action in uppercase if valid, otherwise throws an exception. 2552 * 2553 * @param string $action The foreign key referential action. 2554 * 2555 * @return string 2556 * 2557 * @throws InvalidArgumentException If unknown referential action given. 2558 */ 2559 public function getForeignKeyReferentialActionSQL($action) 2560 { 2561 $upper = strtoupper($action); 2562 switch ($upper) { 2563 case 'CASCADE': 2564 case 'SET NULL': 2565 case 'NO ACTION': 2566 case 'RESTRICT': 2567 case 'SET DEFAULT': 2568 return $upper; 2569 2570 default: 2571 throw new InvalidArgumentException('Invalid foreign key action: ' . $upper); 2572 } 2573 } 2574 2575 /** 2576 * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint 2577 * of a column declaration to be used in statements like CREATE TABLE. 2578 * 2579 * @return string 2580 * 2581 * @throws InvalidArgumentException 2582 */ 2583 public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) 2584 { 2585 $sql = ''; 2586 if (strlen($foreignKey->getName())) { 2587 $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; 2588 } 2589 2590 $sql .= 'FOREIGN KEY ('; 2591 2592 if (count($foreignKey->getLocalColumns()) === 0) { 2593 throw new InvalidArgumentException("Incomplete definition. 'local' required."); 2594 } 2595 2596 if (count($foreignKey->getForeignColumns()) === 0) { 2597 throw new InvalidArgumentException("Incomplete definition. 'foreign' required."); 2598 } 2599 2600 if (strlen($foreignKey->getForeignTableName()) === 0) { 2601 throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required."); 2602 } 2603 2604 return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this)) 2605 . ') REFERENCES ' 2606 . $foreignKey->getQuotedForeignTableName($this) . ' (' 2607 . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')'; 2608 } 2609 2610 /** 2611 * Obtains DBMS specific SQL code portion needed to set the UNIQUE constraint 2612 * of a column declaration to be used in statements like CREATE TABLE. 2613 * 2614 * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint 2615 * of a column declaration. 2616 */ 2617 public function getUniqueFieldDeclarationSQL() 2618 { 2619 return 'UNIQUE'; 2620 } 2621 2622 /** 2623 * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET 2624 * of a column declaration to be used in statements like CREATE TABLE. 2625 * 2626 * @param string $charset The name of the charset. 2627 * 2628 * @return string DBMS specific SQL code portion needed to set the CHARACTER SET 2629 * of a column declaration. 2630 */ 2631 public function getColumnCharsetDeclarationSQL($charset) 2632 { 2633 return ''; 2634 } 2635 2636 /** 2637 * Obtains DBMS specific SQL code portion needed to set the COLLATION 2638 * of a column declaration to be used in statements like CREATE TABLE. 2639 * 2640 * @param string $collation The name of the collation. 2641 * 2642 * @return string DBMS specific SQL code portion needed to set the COLLATION 2643 * of a column declaration. 2644 */ 2645 public function getColumnCollationDeclarationSQL($collation) 2646 { 2647 return $this->supportsColumnCollation() ? 'COLLATE ' . $collation : ''; 2648 } 2649 2650 /** 2651 * Whether the platform prefers sequences for ID generation. 2652 * Subclasses should override this method to return TRUE if they prefer sequences. 2653 * 2654 * @return bool 2655 */ 2656 public function prefersSequences() 2657 { 2658 return false; 2659 } 2660 2661 /** 2662 * Whether the platform prefers identity columns (eg. autoincrement) for ID generation. 2663 * Subclasses should override this method to return TRUE if they prefer identity columns. 2664 * 2665 * @return bool 2666 */ 2667 public function prefersIdentityColumns() 2668 { 2669 return false; 2670 } 2671 2672 /** 2673 * Some platforms need the boolean values to be converted. 2674 * 2675 * The default conversion in this implementation converts to integers (false => 0, true => 1). 2676 * 2677 * Note: if the input is not a boolean the original input might be returned. 2678 * 2679 * There are two contexts when converting booleans: Literals and Prepared Statements. 2680 * This method should handle the literal case 2681 * 2682 * @param mixed $item A boolean or an array of them. 2683 * 2684 * @return mixed A boolean database value or an array of them. 2685 */ 2686 public function convertBooleans($item) 2687 { 2688 if (is_array($item)) { 2689 foreach ($item as $k => $value) { 2690 if (! is_bool($value)) { 2691 continue; 2692 } 2693 2694 $item[$k] = (int) $value; 2695 } 2696 } elseif (is_bool($item)) { 2697 $item = (int) $item; 2698 } 2699 2700 return $item; 2701 } 2702 2703 /** 2704 * Some platforms have boolean literals that needs to be correctly converted 2705 * 2706 * The default conversion tries to convert value into bool "(bool)$item" 2707 * 2708 * @param mixed $item 2709 * 2710 * @return bool|null 2711 */ 2712 public function convertFromBoolean($item) 2713 { 2714 return $item === null ? null : (bool) $item; 2715 } 2716 2717 /** 2718 * This method should handle the prepared statements case. When there is no 2719 * distinction, it's OK to use the same method. 2720 * 2721 * Note: if the input is not a boolean the original input might be returned. 2722 * 2723 * @param mixed $item A boolean or an array of them. 2724 * 2725 * @return mixed A boolean database value or an array of them. 2726 */ 2727 public function convertBooleansToDatabaseValue($item) 2728 { 2729 return $this->convertBooleans($item); 2730 } 2731 2732 /** 2733 * Returns the SQL specific for the platform to get the current date. 2734 * 2735 * @return string 2736 */ 2737 public function getCurrentDateSQL() 2738 { 2739 return 'CURRENT_DATE'; 2740 } 2741 2742 /** 2743 * Returns the SQL specific for the platform to get the current time. 2744 * 2745 * @return string 2746 */ 2747 public function getCurrentTimeSQL() 2748 { 2749 return 'CURRENT_TIME'; 2750 } 2751 2752 /** 2753 * Returns the SQL specific for the platform to get the current timestamp 2754 * 2755 * @return string 2756 */ 2757 public function getCurrentTimestampSQL() 2758 { 2759 return 'CURRENT_TIMESTAMP'; 2760 } 2761 2762 /** 2763 * Returns the SQL for a given transaction isolation level Connection constant. 2764 * 2765 * @param int $level 2766 * 2767 * @return string 2768 * 2769 * @throws InvalidArgumentException 2770 */ 2771 protected function _getTransactionIsolationLevelSQL($level) 2772 { 2773 switch ($level) { 2774 case TransactionIsolationLevel::READ_UNCOMMITTED: 2775 return 'READ UNCOMMITTED'; 2776 2777 case TransactionIsolationLevel::READ_COMMITTED: 2778 return 'READ COMMITTED'; 2779 2780 case TransactionIsolationLevel::REPEATABLE_READ: 2781 return 'REPEATABLE READ'; 2782 2783 case TransactionIsolationLevel::SERIALIZABLE: 2784 return 'SERIALIZABLE'; 2785 2786 default: 2787 throw new InvalidArgumentException('Invalid isolation level:' . $level); 2788 } 2789 } 2790 2791 /** 2792 * @return string 2793 * 2794 * @throws DBALException If not supported on this platform. 2795 */ 2796 public function getListDatabasesSQL() 2797 { 2798 throw DBALException::notSupported(__METHOD__); 2799 } 2800 2801 /** 2802 * Returns the SQL statement for retrieving the namespaces defined in the database. 2803 * 2804 * @return string 2805 * 2806 * @throws DBALException If not supported on this platform. 2807 */ 2808 public function getListNamespacesSQL() 2809 { 2810 throw DBALException::notSupported(__METHOD__); 2811 } 2812 2813 /** 2814 * @param string $database 2815 * 2816 * @return string 2817 * 2818 * @throws DBALException If not supported on this platform. 2819 */ 2820 public function getListSequencesSQL($database) 2821 { 2822 throw DBALException::notSupported(__METHOD__); 2823 } 2824 2825 /** 2826 * @param string $table 2827 * 2828 * @return string 2829 * 2830 * @throws DBALException If not supported on this platform. 2831 */ 2832 public function getListTableConstraintsSQL($table) 2833 { 2834 throw DBALException::notSupported(__METHOD__); 2835 } 2836 2837 /** 2838 * @param string $table 2839 * @param string $database 2840 * 2841 * @return string 2842 * 2843 * @throws DBALException If not supported on this platform. 2844 */ 2845 public function getListTableColumnsSQL($table, $database = null) 2846 { 2847 throw DBALException::notSupported(__METHOD__); 2848 } 2849 2850 /** 2851 * @return string 2852 * 2853 * @throws DBALException If not supported on this platform. 2854 */ 2855 public function getListTablesSQL() 2856 { 2857 throw DBALException::notSupported(__METHOD__); 2858 } 2859 2860 /** 2861 * @return string 2862 * 2863 * @throws DBALException If not supported on this platform. 2864 */ 2865 public function getListUsersSQL() 2866 { 2867 throw DBALException::notSupported(__METHOD__); 2868 } 2869 2870 /** 2871 * Returns the SQL to list all views of a database or user. 2872 * 2873 * @param string $database 2874 * 2875 * @return string 2876 * 2877 * @throws DBALException If not supported on this platform. 2878 */ 2879 public function getListViewsSQL($database) 2880 { 2881 throw DBALException::notSupported(__METHOD__); 2882 } 2883 2884 /** 2885 * Returns the list of indexes for the current database. 2886 * 2887 * The current database parameter is optional but will always be passed 2888 * when using the SchemaManager API and is the database the given table is in. 2889 * 2890 * Attention: Some platforms only support currentDatabase when they 2891 * are connected with that database. Cross-database information schema 2892 * requests may be impossible. 2893 * 2894 * @param string $table 2895 * @param string $database 2896 * 2897 * @return string 2898 * 2899 * @throws DBALException If not supported on this platform. 2900 */ 2901 public function getListTableIndexesSQL($table, $database = null) 2902 { 2903 throw DBALException::notSupported(__METHOD__); 2904 } 2905 2906 /** 2907 * @param string $table 2908 * 2909 * @return string 2910 * 2911 * @throws DBALException If not supported on this platform. 2912 */ 2913 public function getListTableForeignKeysSQL($table) 2914 { 2915 throw DBALException::notSupported(__METHOD__); 2916 } 2917 2918 /** 2919 * @param string $name 2920 * @param string $sql 2921 * 2922 * @return string 2923 * 2924 * @throws DBALException If not supported on this platform. 2925 */ 2926 public function getCreateViewSQL($name, $sql) 2927 { 2928 throw DBALException::notSupported(__METHOD__); 2929 } 2930 2931 /** 2932 * @param string $name 2933 * 2934 * @return string 2935 * 2936 * @throws DBALException If not supported on this platform. 2937 */ 2938 public function getDropViewSQL($name) 2939 { 2940 throw DBALException::notSupported(__METHOD__); 2941 } 2942 2943 /** 2944 * Returns the SQL snippet to drop an existing sequence. 2945 * 2946 * @param Sequence|string $sequence 2947 * 2948 * @return string 2949 * 2950 * @throws DBALException If not supported on this platform. 2951 */ 2952 public function getDropSequenceSQL($sequence) 2953 { 2954 throw DBALException::notSupported(__METHOD__); 2955 } 2956 2957 /** 2958 * @param string $sequence 2959 * 2960 * @return string 2961 * 2962 * @throws DBALException If not supported on this platform. 2963 */ 2964 public function getSequenceNextValSQL($sequence) 2965 { 2966 throw DBALException::notSupported(__METHOD__); 2967 } 2968 2969 /** 2970 * Returns the SQL to create a new database. 2971 * 2972 * @param string $database The name of the database that should be created. 2973 * 2974 * @return string 2975 * 2976 * @throws DBALException If not supported on this platform. 2977 */ 2978 public function getCreateDatabaseSQL($database) 2979 { 2980 throw DBALException::notSupported(__METHOD__); 2981 } 2982 2983 /** 2984 * Returns the SQL to set the transaction isolation level. 2985 * 2986 * @param int $level 2987 * 2988 * @return string 2989 * 2990 * @throws DBALException If not supported on this platform. 2991 */ 2992 public function getSetTransactionIsolationSQL($level) 2993 { 2994 throw DBALException::notSupported(__METHOD__); 2995 } 2996 2997 /** 2998 * Obtains DBMS specific SQL to be used to create datetime columns in 2999 * statements like CREATE TABLE. 3000 * 3001 * @param mixed[] $column 3002 * 3003 * @return string 3004 * 3005 * @throws DBALException If not supported on this platform. 3006 */ 3007 public function getDateTimeTypeDeclarationSQL(array $column) 3008 { 3009 throw DBALException::notSupported(__METHOD__); 3010 } 3011 3012 /** 3013 * Obtains DBMS specific SQL to be used to create datetime with timezone offset columns. 3014 * 3015 * @param mixed[] $column 3016 * 3017 * @return string 3018 */ 3019 public function getDateTimeTzTypeDeclarationSQL(array $column) 3020 { 3021 return $this->getDateTimeTypeDeclarationSQL($column); 3022 } 3023 3024 /** 3025 * Obtains DBMS specific SQL to be used to create date columns in statements 3026 * like CREATE TABLE. 3027 * 3028 * @param mixed[] $column 3029 * 3030 * @return string 3031 * 3032 * @throws DBALException If not supported on this platform. 3033 */ 3034 public function getDateTypeDeclarationSQL(array $column) 3035 { 3036 throw DBALException::notSupported(__METHOD__); 3037 } 3038 3039 /** 3040 * Obtains DBMS specific SQL to be used to create time columns in statements 3041 * like CREATE TABLE. 3042 * 3043 * @param mixed[] $column 3044 * 3045 * @return string 3046 * 3047 * @throws DBALException If not supported on this platform. 3048 */ 3049 public function getTimeTypeDeclarationSQL(array $column) 3050 { 3051 throw DBALException::notSupported(__METHOD__); 3052 } 3053 3054 /** 3055 * @param mixed[] $column 3056 * 3057 * @return string 3058 */ 3059 public function getFloatDeclarationSQL(array $column) 3060 { 3061 return 'DOUBLE PRECISION'; 3062 } 3063 3064 /** 3065 * Gets the default transaction isolation level of the platform. 3066 * 3067 * @see TransactionIsolationLevel 3068 * 3069 * @return int The default isolation level. 3070 */ 3071 public function getDefaultTransactionIsolationLevel() 3072 { 3073 return TransactionIsolationLevel::READ_COMMITTED; 3074 } 3075 3076 /* supports*() methods */ 3077 3078 /** 3079 * Whether the platform supports sequences. 3080 * 3081 * @return bool 3082 */ 3083 public function supportsSequences() 3084 { 3085 return false; 3086 } 3087 3088 /** 3089 * Whether the platform supports identity columns. 3090 * 3091 * Identity columns are columns that receive an auto-generated value from the 3092 * database on insert of a row. 3093 * 3094 * @return bool 3095 */ 3096 public function supportsIdentityColumns() 3097 { 3098 return false; 3099 } 3100 3101 /** 3102 * Whether the platform emulates identity columns through sequences. 3103 * 3104 * Some platforms that do not support identity columns natively 3105 * but support sequences can emulate identity columns by using 3106 * sequences. 3107 * 3108 * @return bool 3109 */ 3110 public function usesSequenceEmulatedIdentityColumns() 3111 { 3112 return false; 3113 } 3114 3115 /** 3116 * Returns the name of the sequence for a particular identity column in a particular table. 3117 * 3118 * @see usesSequenceEmulatedIdentityColumns 3119 * 3120 * @param string $tableName The name of the table to return the sequence name for. 3121 * @param string $columnName The name of the identity column in the table to return the sequence name for. 3122 * 3123 * @return string 3124 * 3125 * @throws DBALException If not supported on this platform. 3126 */ 3127 public function getIdentitySequenceName($tableName, $columnName) 3128 { 3129 throw DBALException::notSupported(__METHOD__); 3130 } 3131 3132 /** 3133 * Whether the platform supports indexes. 3134 * 3135 * @return bool 3136 */ 3137 public function supportsIndexes() 3138 { 3139 return true; 3140 } 3141 3142 /** 3143 * Whether the platform supports partial indexes. 3144 * 3145 * @return bool 3146 */ 3147 public function supportsPartialIndexes() 3148 { 3149 return false; 3150 } 3151 3152 /** 3153 * Whether the platform supports indexes with column length definitions. 3154 */ 3155 public function supportsColumnLengthIndexes(): bool 3156 { 3157 return false; 3158 } 3159 3160 /** 3161 * Whether the platform supports altering tables. 3162 * 3163 * @return bool 3164 */ 3165 public function supportsAlterTable() 3166 { 3167 return true; 3168 } 3169 3170 /** 3171 * Whether the platform supports transactions. 3172 * 3173 * @return bool 3174 */ 3175 public function supportsTransactions() 3176 { 3177 return true; 3178 } 3179 3180 /** 3181 * Whether the platform supports savepoints. 3182 * 3183 * @return bool 3184 */ 3185 public function supportsSavepoints() 3186 { 3187 return true; 3188 } 3189 3190 /** 3191 * Whether the platform supports releasing savepoints. 3192 * 3193 * @return bool 3194 */ 3195 public function supportsReleaseSavepoints() 3196 { 3197 return $this->supportsSavepoints(); 3198 } 3199 3200 /** 3201 * Whether the platform supports primary key constraints. 3202 * 3203 * @return bool 3204 */ 3205 public function supportsPrimaryConstraints() 3206 { 3207 return true; 3208 } 3209 3210 /** 3211 * Whether the platform supports foreign key constraints. 3212 * 3213 * @return bool 3214 */ 3215 public function supportsForeignKeyConstraints() 3216 { 3217 return true; 3218 } 3219 3220 /** 3221 * Whether this platform supports onUpdate in foreign key constraints. 3222 * 3223 * @return bool 3224 */ 3225 public function supportsForeignKeyOnUpdate() 3226 { 3227 return $this->supportsForeignKeyConstraints(); 3228 } 3229 3230 /** 3231 * Whether the platform supports database schemas. 3232 * 3233 * @return bool 3234 */ 3235 public function supportsSchemas() 3236 { 3237 return false; 3238 } 3239 3240 /** 3241 * Whether this platform can emulate schemas. 3242 * 3243 * Platforms that either support or emulate schemas don't automatically 3244 * filter a schema for the namespaced elements in {@link AbstractManager::createSchema()}. 3245 * 3246 * @return bool 3247 */ 3248 public function canEmulateSchemas() 3249 { 3250 return false; 3251 } 3252 3253 /** 3254 * Returns the default schema name. 3255 * 3256 * @return string 3257 * 3258 * @throws DBALException If not supported on this platform. 3259 */ 3260 public function getDefaultSchemaName() 3261 { 3262 throw DBALException::notSupported(__METHOD__); 3263 } 3264 3265 /** 3266 * Whether this platform supports create database. 3267 * 3268 * Some databases don't allow to create and drop databases at all or only with certain tools. 3269 * 3270 * @return bool 3271 */ 3272 public function supportsCreateDropDatabase() 3273 { 3274 return true; 3275 } 3276 3277 /** 3278 * Whether the platform supports getting the affected rows of a recent update/delete type query. 3279 * 3280 * @return bool 3281 */ 3282 public function supportsGettingAffectedRows() 3283 { 3284 return true; 3285 } 3286 3287 /** 3288 * Whether this platform support to add inline column comments as postfix. 3289 * 3290 * @return bool 3291 */ 3292 public function supportsInlineColumnComments() 3293 { 3294 return false; 3295 } 3296 3297 /** 3298 * Whether this platform support the proprietary syntax "COMMENT ON asset". 3299 * 3300 * @return bool 3301 */ 3302 public function supportsCommentOnStatement() 3303 { 3304 return false; 3305 } 3306 3307 /** 3308 * Does this platform have native guid type. 3309 * 3310 * @return bool 3311 */ 3312 public function hasNativeGuidType() 3313 { 3314 return false; 3315 } 3316 3317 /** 3318 * Does this platform have native JSON type. 3319 * 3320 * @return bool 3321 */ 3322 public function hasNativeJsonType() 3323 { 3324 return false; 3325 } 3326 3327 /** 3328 * @deprecated 3329 * 3330 * @return string 3331 * 3332 * @todo Remove in 3.0 3333 */ 3334 public function getIdentityColumnNullInsertSQL() 3335 { 3336 return ''; 3337 } 3338 3339 /** 3340 * Whether this platform supports views. 3341 * 3342 * @return bool 3343 */ 3344 public function supportsViews() 3345 { 3346 return true; 3347 } 3348 3349 /** 3350 * Does this platform support column collation? 3351 * 3352 * @return bool 3353 */ 3354 public function supportsColumnCollation() 3355 { 3356 return false; 3357 } 3358 3359 /** 3360 * Gets the format string, as accepted by the date() function, that describes 3361 * the format of a stored datetime value of this platform. 3362 * 3363 * @return string The format string. 3364 */ 3365 public function getDateTimeFormatString() 3366 { 3367 return 'Y-m-d H:i:s'; 3368 } 3369 3370 /** 3371 * Gets the format string, as accepted by the date() function, that describes 3372 * the format of a stored datetime with timezone value of this platform. 3373 * 3374 * @return string The format string. 3375 */ 3376 public function getDateTimeTzFormatString() 3377 { 3378 return 'Y-m-d H:i:s'; 3379 } 3380 3381 /** 3382 * Gets the format string, as accepted by the date() function, that describes 3383 * the format of a stored date value of this platform. 3384 * 3385 * @return string The format string. 3386 */ 3387 public function getDateFormatString() 3388 { 3389 return 'Y-m-d'; 3390 } 3391 3392 /** 3393 * Gets the format string, as accepted by the date() function, that describes 3394 * the format of a stored time value of this platform. 3395 * 3396 * @return string The format string. 3397 */ 3398 public function getTimeFormatString() 3399 { 3400 return 'H:i:s'; 3401 } 3402 3403 /** 3404 * Adds an driver-specific LIMIT clause to the query. 3405 * 3406 * @param string $query 3407 * @param int|null $limit 3408 * @param int|null $offset 3409 * 3410 * @return string 3411 * 3412 * @throws DBALException 3413 */ 3414 final public function modifyLimitQuery($query, $limit, $offset = null) 3415 { 3416 if ($limit !== null) { 3417 $limit = (int) $limit; 3418 } 3419 3420 $offset = (int) $offset; 3421 3422 if ($offset < 0) { 3423 throw new DBALException(sprintf( 3424 'Offset must be a positive integer or zero, %d given', 3425 $offset 3426 )); 3427 } 3428 3429 if ($offset > 0 && ! $this->supportsLimitOffset()) { 3430 throw new DBALException(sprintf( 3431 'Platform %s does not support offset values in limit queries.', 3432 $this->getName() 3433 )); 3434 } 3435 3436 return $this->doModifyLimitQuery($query, $limit, $offset); 3437 } 3438 3439 /** 3440 * Adds an platform-specific LIMIT clause to the query. 3441 * 3442 * @param string $query 3443 * @param int|null $limit 3444 * @param int|null $offset 3445 * 3446 * @return string 3447 */ 3448 protected function doModifyLimitQuery($query, $limit, $offset) 3449 { 3450 if ($limit !== null) { 3451 $query .= ' LIMIT ' . $limit; 3452 } 3453 3454 if ($offset > 0) { 3455 $query .= ' OFFSET ' . $offset; 3456 } 3457 3458 return $query; 3459 } 3460 3461 /** 3462 * Whether the database platform support offsets in modify limit clauses. 3463 * 3464 * @return bool 3465 */ 3466 public function supportsLimitOffset() 3467 { 3468 return true; 3469 } 3470 3471 /** 3472 * Gets the character casing of a column in an SQL result set of this platform. 3473 * 3474 * @param string $column The column name for which to get the correct character casing. 3475 * 3476 * @return string The column name in the character casing used in SQL result sets. 3477 */ 3478 public function getSQLResultCasing($column) 3479 { 3480 return $column; 3481 } 3482 3483 /** 3484 * Makes any fixes to a name of a schema element (table, sequence, ...) that are required 3485 * by restrictions of the platform, like a maximum length. 3486 * 3487 * @param string $schemaElementName 3488 * 3489 * @return string 3490 */ 3491 public function fixSchemaElementName($schemaElementName) 3492 { 3493 return $schemaElementName; 3494 } 3495 3496 /** 3497 * Maximum length of any given database identifier, like tables or column names. 3498 * 3499 * @return int 3500 */ 3501 public function getMaxIdentifierLength() 3502 { 3503 return 63; 3504 } 3505 3506 /** 3507 * Returns the insert SQL for an empty insert statement. 3508 * 3509 * @param string $quotedTableName 3510 * @param string $quotedIdentifierColumnName 3511 * 3512 * @return string 3513 */ 3514 public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) 3515 { 3516 return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (null)'; 3517 } 3518 3519 /** 3520 * Generates a Truncate Table SQL statement for a given table. 3521 * 3522 * Cascade is not supported on many platforms but would optionally cascade the truncate by 3523 * following the foreign keys. 3524 * 3525 * @param string $tableName 3526 * @param bool $cascade 3527 * 3528 * @return string 3529 */ 3530 public function getTruncateTableSQL($tableName, $cascade = false) 3531 { 3532 $tableIdentifier = new Identifier($tableName); 3533 3534 return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); 3535 } 3536 3537 /** 3538 * This is for test reasons, many vendors have special requirements for dummy statements. 3539 * 3540 * @return string 3541 */ 3542 public function getDummySelectSQL() 3543 { 3544 $expression = func_num_args() > 0 ? func_get_arg(0) : '1'; 3545 3546 return sprintf('SELECT %s', $expression); 3547 } 3548 3549 /** 3550 * Returns the SQL to create a new savepoint. 3551 * 3552 * @param string $savepoint 3553 * 3554 * @return string 3555 */ 3556 public function createSavePoint($savepoint) 3557 { 3558 return 'SAVEPOINT ' . $savepoint; 3559 } 3560 3561 /** 3562 * Returns the SQL to release a savepoint. 3563 * 3564 * @param string $savepoint 3565 * 3566 * @return string 3567 */ 3568 public function releaseSavePoint($savepoint) 3569 { 3570 return 'RELEASE SAVEPOINT ' . $savepoint; 3571 } 3572 3573 /** 3574 * Returns the SQL to rollback a savepoint. 3575 * 3576 * @param string $savepoint 3577 * 3578 * @return string 3579 */ 3580 public function rollbackSavePoint($savepoint) 3581 { 3582 return 'ROLLBACK TO SAVEPOINT ' . $savepoint; 3583 } 3584 3585 /** 3586 * Returns the keyword list instance of this platform. 3587 * 3588 * @return KeywordList 3589 * 3590 * @throws DBALException If no keyword list is specified. 3591 */ 3592 final public function getReservedKeywordsList() 3593 { 3594 // Check for an existing instantiation of the keywords class. 3595 if ($this->_keywords) { 3596 return $this->_keywords; 3597 } 3598 3599 $class = $this->getReservedKeywordsClass(); 3600 $keywords = new $class(); 3601 if (! $keywords instanceof KeywordList) { 3602 throw DBALException::notSupported(__METHOD__); 3603 } 3604 3605 // Store the instance so it doesn't need to be generated on every request. 3606 $this->_keywords = $keywords; 3607 3608 return $keywords; 3609 } 3610 3611 /** 3612 * Returns the class name of the reserved keywords list. 3613 * 3614 * @return string 3615 * 3616 * @throws DBALException If not supported on this platform. 3617 */ 3618 protected function getReservedKeywordsClass() 3619 { 3620 throw DBALException::notSupported(__METHOD__); 3621 } 3622 3623 /** 3624 * Quotes a literal string. 3625 * This method is NOT meant to fix SQL injections! 3626 * It is only meant to escape this platform's string literal 3627 * quote character inside the given literal string. 3628 * 3629 * @param string $str The literal string to be quoted. 3630 * 3631 * @return string The quoted literal string. 3632 */ 3633 public function quoteStringLiteral($str) 3634 { 3635 $c = $this->getStringLiteralQuoteCharacter(); 3636 3637 return $c . str_replace($c, $c . $c, $str) . $c; 3638 } 3639 3640 /** 3641 * Gets the character used for string literal quoting. 3642 * 3643 * @return string 3644 */ 3645 public function getStringLiteralQuoteCharacter() 3646 { 3647 return "'"; 3648 } 3649 3650 /** 3651 * Escapes metacharacters in a string intended to be used with a LIKE 3652 * operator. 3653 * 3654 * @param string $inputString a literal, unquoted string 3655 * @param string $escapeChar should be reused by the caller in the LIKE 3656 * expression. 3657 */ 3658 final public function escapeStringForLike(string $inputString, string $escapeChar): string 3659 { 3660 return preg_replace( 3661 '~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u', 3662 addcslashes($escapeChar, '\\') . '$1', 3663 $inputString 3664 ); 3665 } 3666 3667 protected function getLikeWildcardCharacters(): string 3668 { 3669 return '%_'; 3670 } 3671} 3672