AbstractPlatform.php 101 KB
Newer Older
1 2
<?php

3 4
namespace Doctrine\DBAL\Platforms;

Grégoire Paris's avatar
Grégoire Paris committed
5 6 7 8 9 10 11 12 13 14 15
use Doctrine\Common\EventManager;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs;
use Doctrine\DBAL\Event\SchemaAlterTableChangeColumnEventArgs;
use Doctrine\DBAL\Event\SchemaAlterTableEventArgs;
use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs;
use Doctrine\DBAL\Event\SchemaAlterTableRenameColumnEventArgs;
use Doctrine\DBAL\Event\SchemaCreateTableColumnEventArgs;
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
use Doctrine\DBAL\Event\SchemaDropTableEventArgs;
use Doctrine\DBAL\Events;
16
use Doctrine\DBAL\Platforms\Keywords\KeywordList;
Grégoire Paris's avatar
Grégoire Paris committed
17 18
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ColumnDiff;
Benjamin Morel's avatar
Benjamin Morel committed
19
use Doctrine\DBAL\Schema\Constraint;
Grégoire Paris's avatar
Grégoire Paris committed
20 21 22
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Identifier;
use Doctrine\DBAL\Schema\Index;
Benjamin Morel's avatar
Benjamin Morel committed
23 24 25
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
26
use Doctrine\DBAL\TransactionIsolationLevel;
Grégoire Paris's avatar
Grégoire Paris committed
27
use Doctrine\DBAL\Types;
Benjamin Morel's avatar
Benjamin Morel committed
28
use Doctrine\DBAL\Types\Type;
29
use InvalidArgumentException;
Sergei Morozov's avatar
Sergei Morozov committed
30
use UnexpectedValueException;
31
use const E_USER_DEPRECATED;
32
use function addcslashes;
33 34 35 36
use function array_map;
use function array_merge;
use function array_unique;
use function array_values;
Sergei Morozov's avatar
Sergei Morozov committed
37
use function assert;
38 39
use function count;
use function explode;
40
use function func_get_arg;
41
use function func_get_args;
42
use function func_num_args;
43 44 45 46 47 48
use function implode;
use function in_array;
use function is_array;
use function is_bool;
use function is_int;
use function is_string;
49 50
use function preg_quote;
use function preg_replace;
51
use function sprintf;
52
use function str_replace;
53
use function strlen;
54 55 56
use function strpos;
use function strtolower;
use function strtoupper;
57
use function trigger_error;
58 59 60 61 62 63

/**
 * Base class for all DatabasePlatforms. The DatabasePlatforms are the central
 * point of abstraction of platform-specific behaviors, features and SQL dialects.
 * They are a passive source of information.
 *
64
 * @todo Remove any unnecessary methods.
65
 */
66
abstract class AbstractPlatform
67
{
68
    public const CREATE_INDEXES = 1;
69

70
    public const CREATE_FOREIGNKEYS = 2;
71

72
    /**
73
     * @deprecated Use DateIntervalUnit::INTERVAL_UNIT_SECOND.
74
     */
75
    public const DATE_INTERVAL_UNIT_SECOND = DateIntervalUnit::SECOND;
76 77

    /**
78
     * @deprecated Use DateIntervalUnit::MINUTE.
79
     */
80
    public const DATE_INTERVAL_UNIT_MINUTE = DateIntervalUnit::MINUTE;
81 82

    /**
83
     * @deprecated Use DateIntervalUnit::HOUR.
84
     */
85
    public const DATE_INTERVAL_UNIT_HOUR = DateIntervalUnit::HOUR;
86 87

    /**
88
     * @deprecated Use DateIntervalUnit::DAY.
89
     */
90
    public const DATE_INTERVAL_UNIT_DAY = DateIntervalUnit::DAY;
91 92

    /**
93
     * @deprecated Use DateIntervalUnit::WEEK.
94
     */
95
    public const DATE_INTERVAL_UNIT_WEEK = DateIntervalUnit::WEEK;
96 97

    /**
98
     * @deprecated Use DateIntervalUnit::MONTH.
99
     */
100
    public const DATE_INTERVAL_UNIT_MONTH = DateIntervalUnit::MONTH;
101 102

    /**
103
     * @deprecated Use DateIntervalUnit::QUARTER.
104
     */
105
    public const DATE_INTERVAL_UNIT_QUARTER = DateIntervalUnit::QUARTER;
106 107

    /**
108
     * @deprecated Use DateIntervalUnit::QUARTER.
109
     */
110
    public const DATE_INTERVAL_UNIT_YEAR = DateIntervalUnit::YEAR;
111

112
    /**
113
     * @deprecated Use TrimMode::UNSPECIFIED.
114
     */
115
    public const TRIM_UNSPECIFIED = TrimMode::UNSPECIFIED;
116 117

    /**
118
     * @deprecated Use TrimMode::LEADING.
119
     */
120
    public const TRIM_LEADING = TrimMode::LEADING;
121 122

    /**
123
     * @deprecated Use TrimMode::TRAILING.
124
     */
125
    public const TRIM_TRAILING = TrimMode::TRAILING;
126 127

    /**
128
     * @deprecated Use TrimMode::BOTH.
129
     */
130
    public const TRIM_BOTH = TrimMode::BOTH;
131

132
    /** @var string[]|null */
133 134
    protected $doctrineTypeMapping = null;

135 136 137 138
    /**
     * Contains a list of all columns that should generate parseable column comments for type-detection
     * in reverse engineering scenarios.
     *
139
     * @var string[]|null
140 141 142
     */
    protected $doctrineTypeComments = null;

143
    /** @var EventManager */
144 145
    protected $_eventManager;

146 147 148
    /**
     * Holds the KeywordList instance for the current platform.
     *
Sergei Morozov's avatar
Sergei Morozov committed
149
     * @var KeywordList|null
150 151 152
     */
    protected $_keywords;

Benjamin Morel's avatar
Benjamin Morel committed
153 154 155
    public function __construct()
    {
    }
156

157 158 159 160 161 162 163 164 165 166 167
    /**
     * Sets the EventManager used by the Platform.
     */
    public function setEventManager(EventManager $eventManager)
    {
        $this->_eventManager = $eventManager;
    }

    /**
     * Gets the EventManager used by the Platform.
     *
168
     * @return EventManager
169 170 171 172 173 174
     */
    public function getEventManager()
    {
        return $this->_eventManager;
    }

175
    /**
Benjamin Morel's avatar
Benjamin Morel committed
176
     * Returns the SQL snippet that declares a boolean column.
177
     *
178
     * @param mixed[] $columnDef
179
     *
180 181 182 183 184
     * @return string
     */
    abstract public function getBooleanTypeDeclarationSQL(array $columnDef);

    /**
Benjamin Morel's avatar
Benjamin Morel committed
185
     * Returns the SQL snippet that declares a 4 byte integer column.
186
     *
187
     * @param mixed[] $columnDef
188
     *
189 190 191 192 193
     * @return string
     */
    abstract public function getIntegerTypeDeclarationSQL(array $columnDef);

    /**
Benjamin Morel's avatar
Benjamin Morel committed
194
     * Returns the SQL snippet that declares an 8 byte integer column.
195
     *
196
     * @param mixed[] $columnDef
197
     *
198 199 200 201 202
     * @return string
     */
    abstract public function getBigIntTypeDeclarationSQL(array $columnDef);

    /**
Benjamin Morel's avatar
Benjamin Morel committed
203
     * Returns the SQL snippet that declares a 2 byte integer column.
204
     *
205
     * @param mixed[] $columnDef
206
     *
207 208 209 210 211
     * @return string
     */
    abstract public function getSmallIntTypeDeclarationSQL(array $columnDef);

    /**
Benjamin Morel's avatar
Benjamin Morel committed
212
     * Returns the SQL snippet that declares common properties of an integer column.
213
     *
214
     * @param mixed[] $columnDef
Benjamin Morel's avatar
Benjamin Morel committed
215
     *
216 217 218 219 220
     * @return string
     */
    abstract protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef);

    /**
Benjamin Morel's avatar
Benjamin Morel committed
221
     * Lazy load Doctrine Type Mappings.
222 223 224 225 226
     *
     * @return void
     */
    abstract protected function initializeDoctrineTypeMappings();

227
    /**
Benjamin Morel's avatar
Benjamin Morel committed
228
     * Initializes Doctrine Type Mappings with the platform defaults
229
     * and with all additional type mappings.
Benjamin Morel's avatar
Benjamin Morel committed
230 231
     *
     * @return void
232 233 234 235 236 237 238 239 240 241 242 243
     */
    private function initializeAllDoctrineTypeMappings()
    {
        $this->initializeDoctrineTypeMappings();

        foreach (Type::getTypesMap() as $typeName => $className) {
            foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) {
                $this->doctrineTypeMapping[$dbType] = $typeName;
            }
        }
    }

244
    /**
Benjamin Morel's avatar
Benjamin Morel committed
245
     * Returns the SQL snippet used to declare a VARCHAR column type.
246
     *
247
     * @param mixed[] $field
248
     *
Christophe Coevoet's avatar
Christophe Coevoet committed
249
     * @return string
250
     */
251 252
    public function getVarcharTypeDeclarationSQL(array $field)
    {
253
        if (! isset($field['length'])) {
254 255 256
            $field['length'] = $this->getVarcharDefaultLength();
        }

257
        $fixed = $field['fixed'] ?? false;
258

Sergei Morozov's avatar
Sergei Morozov committed
259 260 261 262 263
        $maxLength = $fixed
            ? $this->getCharMaxLength()
            : $this->getVarcharMaxLength();

        if ($field['length'] > $maxLength) {
264 265
            return $this->getClobTypeDeclarationSQL($field);
        }
266 267

        return $this->getVarcharTypeDeclarationSQLSnippet($field['length'], $fixed);
268 269
    }

Steve Müller's avatar
Steve Müller committed
270 271 272
    /**
     * Returns the SQL snippet used to declare a BINARY/VARBINARY column type.
     *
273
     * @param mixed[] $field The column definition.
Steve Müller's avatar
Steve Müller committed
274 275 276 277 278
     *
     * @return string
     */
    public function getBinaryTypeDeclarationSQL(array $field)
    {
279
        if (! isset($field['length'])) {
Steve Müller's avatar
Steve Müller committed
280 281 282
            $field['length'] = $this->getBinaryDefaultLength();
        }

283
        $fixed = $field['fixed'] ?? false;
Steve Müller's avatar
Steve Müller committed
284

285 286 287 288 289
        $maxLength = $this->getBinaryMaxLength();

        if ($field['length'] > $maxLength) {
            if ($maxLength > 0) {
                @trigger_error(sprintf(
290
                    'Binary field length %d is greater than supported by the platform (%d). Reduce the field length or use a BLOB field instead.',
291 292 293 294 295
                    $field['length'],
                    $maxLength
                ), E_USER_DEPRECATED);
            }

Steve Müller's avatar
Steve Müller committed
296 297 298 299 300 301
            return $this->getBlobTypeDeclarationSQL($field);
        }

        return $this->getBinaryTypeDeclarationSQLSnippet($field['length'], $fixed);
    }

302
    /**
Benjamin Morel's avatar
Benjamin Morel committed
303
     * Returns the SQL snippet to declare a GUID/UUID field.
304
     *
305
     * By default this maps directly to a CHAR(36) and only maps to more
306 307
     * special datatypes when the underlying databases support this datatype.
     *
308
     * @param mixed[] $field
309
     *
310 311
     * @return string
     */
312
    public function getGuidTypeDeclarationSQL(array $field)
313
    {
314 315 316
        $field['length'] = 36;
        $field['fixed']  = true;

317 318 319
        return $this->getVarcharTypeDeclarationSQL($field);
    }

320 321 322 323 324 325
    /**
     * Returns the SQL snippet to declare a JSON field.
     *
     * By default this maps directly to a CLOB and only maps to more
     * special datatypes when the underlying databases support this datatype.
     *
326
     * @param mixed[] $field
327 328 329 330 331 332 333 334
     *
     * @return string
     */
    public function getJsonTypeDeclarationSQL(array $field)
    {
        return $this->getClobTypeDeclarationSQL($field);
    }

Christophe Coevoet's avatar
Christophe Coevoet committed
335
    /**
336 337
     * @param int  $length
     * @param bool $fixed
338
     *
Christophe Coevoet's avatar
Christophe Coevoet committed
339
     * @return string
340
     *
341
     * @throws DBALException If not supported on this platform.
Christophe Coevoet's avatar
Christophe Coevoet committed
342
     */
343 344 345 346
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
    {
        throw DBALException::notSupported('VARCHARs not supported by Platform.');
    }
347

Steve Müller's avatar
Steve Müller committed
348 349 350
    /**
     * Returns the SQL snippet used to declare a BINARY/VARBINARY column type.
     *
351 352
     * @param int  $length The length of the column.
     * @param bool $fixed  Whether the column length is fixed.
Steve Müller's avatar
Steve Müller committed
353 354 355
     *
     * @return string
     *
356
     * @throws DBALException If not supported on this platform.
Steve Müller's avatar
Steve Müller committed
357 358 359 360 361 362
     */
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
    {
        throw DBALException::notSupported('BINARY/VARBINARY column types are not supported by this platform.');
    }

363
    /**
Benjamin Morel's avatar
Benjamin Morel committed
364
     * Returns the SQL snippet used to declare a CLOB column type.
365
     *
366
     * @param mixed[] $field
367 368
     *
     * @return string
369 370 371
     */
    abstract public function getClobTypeDeclarationSQL(array $field);

372
    /**
Benjamin Morel's avatar
Benjamin Morel committed
373
     * Returns the SQL Snippet used to declare a BLOB column type.
374
     *
375
     * @param mixed[] $field
376 377
     *
     * @return string
378 379 380
     */
    abstract public function getBlobTypeDeclarationSQL(array $field);

381 382 383 384 385 386 387
    /**
     * Gets the name of the platform.
     *
     * @return string
     */
    abstract public function getName();

388
    /**
Benjamin Morel's avatar
Benjamin Morel committed
389
     * Registers a doctrine type to be used in conjunction with a column type of this platform.
390 391 392
     *
     * @param string $dbType
     * @param string $doctrineType
393
     *
394
     * @throws DBALException If the type is not found.
395 396 397 398
     */
    public function registerDoctrineTypeMapping($dbType, $doctrineType)
    {
        if ($this->doctrineTypeMapping === null) {
399
            $this->initializeAllDoctrineTypeMappings();
400 401
        }

402
        if (! Types\Type::hasType($doctrineType)) {
403 404 405
            throw DBALException::typeNotFound($doctrineType);
        }

406
        $dbType                             = strtolower($dbType);
407
        $this->doctrineTypeMapping[$dbType] = $doctrineType;
408 409 410

        $doctrineType = Type::getType($doctrineType);

411 412
        if (! $doctrineType->requiresSQLCommentHint($this)) {
            return;
413
        }
414 415

        $this->markDoctrineTypeCommented($doctrineType);
416 417 418
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
419
     * Gets the Doctrine type that is mapped for the given database column type.
420
     *
Benjamin Morel's avatar
Benjamin Morel committed
421
     * @param string $dbType
422
     *
423
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
424
     *
425
     * @throws DBALException
426 427 428 429
     */
    public function getDoctrineTypeMapping($dbType)
    {
        if ($this->doctrineTypeMapping === null) {
430
            $this->initializeAllDoctrineTypeMappings();
431
        }
432

433
        $dbType = strtolower($dbType);
434

435 436
        if (! isset($this->doctrineTypeMapping[$dbType])) {
            throw new DBALException('Unknown database type ' . $dbType . ' requested, ' . static::class . ' may not support it.');
437
        }
438 439

        return $this->doctrineTypeMapping[$dbType];
440 441
    }

442
    /**
Benjamin Morel's avatar
Benjamin Morel committed
443
     * Checks if a database type is currently supported by this platform.
444 445
     *
     * @param string $dbType
446
     *
447
     * @return bool
448 449 450 451
     */
    public function hasDoctrineTypeMappingFor($dbType)
    {
        if ($this->doctrineTypeMapping === null) {
452
            $this->initializeAllDoctrineTypeMappings();
453 454 455
        }

        $dbType = strtolower($dbType);
Benjamin Morel's avatar
Benjamin Morel committed
456

457 458 459
        return isset($this->doctrineTypeMapping[$dbType]);
    }

460
    /**
Benjamin Morel's avatar
Benjamin Morel committed
461
     * Initializes the Doctrine Type comments instance variable for in_array() checks.
462 463 464 465 466
     *
     * @return void
     */
    protected function initializeCommentedDoctrineTypes()
    {
467
        $this->doctrineTypeComments = [];
468 469 470

        foreach (Type::getTypesMap() as $typeName => $className) {
            $type = Type::getType($typeName);
Benjamin Eberlei's avatar
Benjamin Eberlei committed
471

472 473
            if (! $type->requiresSQLCommentHint($this)) {
                continue;
474
            }
475 476

            $this->doctrineTypeComments[] = $typeName;
477
        }
478 479 480 481 482
    }

    /**
     * Is it necessary for the platform to add a parsable type comment to allow reverse engineering the given type?
     *
483
     * @return bool
484 485 486 487 488 489 490
     */
    public function isCommentedDoctrineType(Type $doctrineType)
    {
        if ($this->doctrineTypeComments === null) {
            $this->initializeCommentedDoctrineTypes();
        }

Sergei Morozov's avatar
Sergei Morozov committed
491 492
        assert(is_array($this->doctrineTypeComments));

493 494 495 496
        return in_array($doctrineType->getName(), $this->doctrineTypeComments);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
497
     * Marks this type as to be commented in ALTER TABLE and CREATE TABLE statements.
498
     *
499
     * @param string|Type $doctrineType
500
     *
501 502
     * @return void
     */
503
    public function markDoctrineTypeCommented($doctrineType)
504 505 506 507
    {
        if ($this->doctrineTypeComments === null) {
            $this->initializeCommentedDoctrineTypes();
        }
508

Sergei Morozov's avatar
Sergei Morozov committed
509 510
        assert(is_array($this->doctrineTypeComments));

511
        $this->doctrineTypeComments[] = $doctrineType instanceof Type ? $doctrineType->getName() : $doctrineType;
512 513 514
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
515 516
     * Gets the comment to append to a column comment that helps parsing this type in reverse engineering.
     *
517 518 519 520 521 522 523
     * @return string
     */
    public function getDoctrineTypeComment(Type $doctrineType)
    {
        return '(DC2Type:' . $doctrineType->getName() . ')';
    }

524
    /**
Benjamin Morel's avatar
Benjamin Morel committed
525 526
     * Gets the comment of a passed column modified by potential doctrine type comment hints.
     *
Sergei Morozov's avatar
Sergei Morozov committed
527
     * @return string|null
528 529 530 531
     */
    protected function getColumnComment(Column $column)
    {
        $comment = $column->getComment();
532

533 534 535
        if ($this->isCommentedDoctrineType($column->getType())) {
            $comment .= $this->getDoctrineTypeComment($column->getType());
        }
536

537 538 539
        return $comment;
    }

540 541 542 543 544 545 546 547 548
    /**
     * Gets the character used for identifier quoting.
     *
     * @return string
     */
    public function getIdentifierQuoteCharacter()
    {
        return '"';
    }
549

550 551 552 553 554 555 556
    /**
     * Gets the string portion that starts an SQL comment.
     *
     * @return string
     */
    public function getSqlCommentStartString()
    {
557
        return '--';
558
    }
559

560
    /**
561
     * Gets the string portion that ends an SQL comment.
562 563 564 565 566 567 568
     *
     * @return string
     */
    public function getSqlCommentEndString()
    {
        return "\n";
    }
569

Sergei Morozov's avatar
Sergei Morozov committed
570 571 572 573 574 575 576 577
    /**
     * Gets the maximum length of a char field.
     */
    public function getCharMaxLength() : int
    {
        return $this->getVarcharMaxLength();
    }

578 579 580
    /**
     * Gets the maximum length of a varchar field.
     *
581
     * @return int
582 583
     */
    public function getVarcharMaxLength()
584 585 586 587 588 589 590
    {
        return 4000;
    }

    /**
     * Gets the default length of a varchar field.
     *
591
     * @return int
592 593
     */
    public function getVarcharDefaultLength()
594 595 596
    {
        return 255;
    }
597

Steve Müller's avatar
Steve Müller committed
598 599 600
    /**
     * Gets the maximum length of a binary field.
     *
601
     * @return int
Steve Müller's avatar
Steve Müller committed
602 603 604 605 606 607 608 609 610
     */
    public function getBinaryMaxLength()
    {
        return 4000;
    }

    /**
     * Gets the default length of a binary field.
     *
611
     * @return int
Steve Müller's avatar
Steve Müller committed
612 613 614 615 616 617
     */
    public function getBinaryDefaultLength()
    {
        return 255;
    }

618 619 620
    /**
     * Gets all SQL wildcard characters of the platform.
     *
621
     * @return string[]
622 623 624
     */
    public function getWildcards()
    {
625
        return ['%', '_'];
626
    }
627

628 629 630 631
    /**
     * Returns the regular expression operator.
     *
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
632
     *
633
     * @throws DBALException If not supported on this platform.
634 635 636
     */
    public function getRegexpExpression()
    {
637
        throw DBALException::notSupported(__METHOD__);
638
    }
639

640
    /**
Benjamin Morel's avatar
Benjamin Morel committed
641 642
     * Returns the global unique identifier expression.
     *
643
     * @deprecated Use application-generated UUIDs instead
644
     *
645
     * @return string
646
     *
647
     * @throws DBALException If not supported on this platform.
648 649 650 651
     */
    public function getGuidExpression()
    {
        throw DBALException::notSupported(__METHOD__);
652 653 654
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
655
     * Returns the SQL snippet to get the average value of a column.
656
     *
Benjamin Morel's avatar
Benjamin Morel committed
657
     * @param string $column The column to use.
658
     *
Benjamin Morel's avatar
Benjamin Morel committed
659
     * @return string Generated SQL including an AVG aggregate function.
660 661 662
     */
    public function getAvgExpression($column)
    {
Benjamin Morel's avatar
Benjamin Morel committed
663
        return 'AVG(' . $column . ')';
664 665 666
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
667
     * Returns the SQL snippet to get the number of rows (without a NULL value) of a column.
668
     *
Benjamin Morel's avatar
Benjamin Morel committed
669
     * If a '*' is used instead of a column the number of selected rows is returned.
670
     *
671
     * @param string|int $column The column to use.
672
     *
Benjamin Morel's avatar
Benjamin Morel committed
673
     * @return string Generated SQL including a COUNT aggregate function.
674 675 676 677 678 679 680
     */
    public function getCountExpression($column)
    {
        return 'COUNT(' . $column . ')';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
681 682 683
     * Returns the SQL snippet to get the highest value of a column.
     *
     * @param string $column The column to use.
684
     *
Benjamin Morel's avatar
Benjamin Morel committed
685
     * @return string Generated SQL including a MAX aggregate function.
686 687 688 689 690 691 692
     */
    public function getMaxExpression($column)
    {
        return 'MAX(' . $column . ')';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
693
     * Returns the SQL snippet to get the lowest value of a column.
694
     *
Benjamin Morel's avatar
Benjamin Morel committed
695 696 697
     * @param string $column The column to use.
     *
     * @return string Generated SQL including a MIN aggregate function.
698 699 700 701 702 703 704
     */
    public function getMinExpression($column)
    {
        return 'MIN(' . $column . ')';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
705
     * Returns the SQL snippet to get the total sum of a column.
706
     *
Benjamin Morel's avatar
Benjamin Morel committed
707 708 709
     * @param string $column The column to use.
     *
     * @return string Generated SQL including a SUM aggregate function.
710 711 712 713 714 715 716 717 718
     */
    public function getSumExpression($column)
    {
        return 'SUM(' . $column . ')';
    }

    // scalar functions

    /**
Benjamin Morel's avatar
Benjamin Morel committed
719
     * Returns the SQL snippet to get the md5 sum of a field.
720
     *
Benjamin Morel's avatar
Benjamin Morel committed
721
     * Note: Not SQL92, but common functionality.
722
     *
723
     * @param string $column
Benjamin Morel's avatar
Benjamin Morel committed
724
     *
725 726 727 728 729 730 731 732
     * @return string
     */
    public function getMd5Expression($column)
    {
        return 'MD5(' . $column . ')';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
733
     * Returns the SQL snippet to get the length of a text field.
734
     *
735
     * @param string $column
736
     *
737 738 739 740 741 742 743
     * @return string
     */
    public function getLengthExpression($column)
    {
        return 'LENGTH(' . $column . ')';
    }

744
    /**
Benjamin Morel's avatar
Benjamin Morel committed
745
     * Returns the SQL snippet to get the squared value of a column.
746
     *
Benjamin Morel's avatar
Benjamin Morel committed
747
     * @param string $column The column to use.
748
     *
Benjamin Morel's avatar
Benjamin Morel committed
749
     * @return string Generated SQL including an SQRT aggregate function.
750 751 752 753 754 755
     */
    public function getSqrtExpression($column)
    {
        return 'SQRT(' . $column . ')';
    }

756
    /**
Benjamin Morel's avatar
Benjamin Morel committed
757
     * Returns the SQL snippet to round a numeric field to the number of decimals specified.
758
     *
759 760
     * @param string $column
     * @param int    $decimals
761
     *
762 763 764 765 766 767 768 769
     * @return string
     */
    public function getRoundExpression($column, $decimals = 0)
    {
        return 'ROUND(' . $column . ', ' . $decimals . ')';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
770
     * Returns the SQL snippet to get the remainder of the division operation $expression1 / $expression2.
771 772 773
     *
     * @param string $expression1
     * @param string $expression2
774
     *
775 776 777 778 779 780 781 782
     * @return string
     */
    public function getModExpression($expression1, $expression2)
    {
        return 'MOD(' . $expression1 . ', ' . $expression2 . ')';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
783
     * Returns the SQL snippet to trim a string.
784
     *
785 786 787
     * @param string      $str  The expression to apply the trim to.
     * @param int         $mode The position of the trim (leading/trailing/both).
     * @param string|bool $char The char to trim, has to be quoted already. Defaults to space.
788
     *
789 790
     * @return string
     */
791
    public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false)
792
    {
Steve Müller's avatar
Steve Müller committed
793
        $expression = '';
794

795 796
        switch ($mode) {
            case TrimMode::LEADING:
Steve Müller's avatar
Steve Müller committed
797
                $expression = 'LEADING ';
798 799
                break;

800
            case TrimMode::TRAILING:
Steve Müller's avatar
Steve Müller committed
801
                $expression = 'TRAILING ';
802 803
                break;

804
            case TrimMode::BOTH:
Steve Müller's avatar
Steve Müller committed
805
                $expression = 'BOTH ';
806
                break;
807 808
        }

809
        if ($char !== false) {
Steve Müller's avatar
Steve Müller committed
810 811 812
            $expression .= $char . ' ';
        }

813
        if ($mode || $char !== false) {
Steve Müller's avatar
Steve Müller committed
814 815 816 817
            $expression .= 'FROM ';
        }

        return 'TRIM(' . $expression . $str . ')';
818 819 820
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
821
     * Returns the SQL snippet to trim trailing space characters from the expression.
822
     *
Benjamin Morel's avatar
Benjamin Morel committed
823
     * @param string $str Literal string or column name.
824
     *
825 826 827 828 829 830 831 832
     * @return string
     */
    public function getRtrimExpression($str)
    {
        return 'RTRIM(' . $str . ')';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
833
     * Returns the SQL snippet to trim leading space characters from the expression.
834
     *
Benjamin Morel's avatar
Benjamin Morel committed
835
     * @param string $str Literal string or column name.
836
     *
837 838 839 840 841 842 843 844
     * @return string
     */
    public function getLtrimExpression($str)
    {
        return 'LTRIM(' . $str . ')';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
845 846
     * Returns the SQL snippet to change all characters from the expression to uppercase,
     * according to the current character set mapping.
847
     *
Benjamin Morel's avatar
Benjamin Morel committed
848
     * @param string $str Literal string or column name.
849
     *
850 851 852 853 854 855 856 857
     * @return string
     */
    public function getUpperExpression($str)
    {
        return 'UPPER(' . $str . ')';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
858 859
     * Returns the SQL snippet to change all characters from the expression to lowercase,
     * according to the current character set mapping.
860
     *
Benjamin Morel's avatar
Benjamin Morel committed
861
     * @param string $str Literal string or column name.
862
     *
863 864 865 866 867 868 869 870
     * @return string
     */
    public function getLowerExpression($str)
    {
        return 'LOWER(' . $str . ')';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
871
     * Returns the SQL snippet to get the position of the first occurrence of substring $substr in string $str.
872
     *
Sergei Morozov's avatar
Sergei Morozov committed
873 874 875
     * @param string    $str      Literal string.
     * @param string    $substr   Literal string to find.
     * @param int|false $startPos Position to start at, beginning of string by default.
876
     *
Christophe Coevoet's avatar
Christophe Coevoet committed
877
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
878
     *
879
     * @throws DBALException If not supported on this platform.
880
     */
881
    public function getLocateExpression($str, $substr, $startPos = false)
882
    {
883
        throw DBALException::notSupported(__METHOD__);
884 885 886
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
887
     * Returns the SQL snippet to get the current system date.
888 889 890 891 892 893 894 895 896
     *
     * @return string
     */
    public function getNowExpression()
    {
        return 'NOW()';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
897
     * Returns a SQL snippet to get a substring inside an SQL statement.
898 899 900
     *
     * Note: Not SQL92, but common functionality.
     *
Benjamin Morel's avatar
Benjamin Morel committed
901
     * SQLite only supports the 2 parameter variant of this function.
902
     *
903 904 905
     * @param string   $value  An sql string literal or column name/alias.
     * @param int      $from   Where to start the substring portion.
     * @param int|null $length The substring portion length.
906
     *
907
     * @return string
908
     */
909
    public function getSubstringExpression($value, $from, $length = null)
910
    {
911
        if ($length === null) {
912 913
            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
        }
914 915

        return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')';
916 917 918
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
919
     * Returns a SQL snippet to concatenate the given expressions.
920
     *
Benjamin Morel's avatar
Benjamin Morel committed
921
     * Accepts an arbitrary number of string parameters. Each parameter must contain an expression.
922
     *
923 924 925 926
     * @return string
     */
    public function getConcatExpression()
    {
927
        return implode(' || ', func_get_args());
928 929 930 931 932 933 934 935 936 937 938 939 940
    }

    /**
     * Returns the SQL for a logical not.
     *
     * Example:
     * <code>
     * $q = new Doctrine_Query();
     * $e = $q->expr;
     * $q->select('*')->from('table')
     *   ->where($e->eq('id', $e->not('null'));
     * </code>
     *
941
     * @param string $expression
942
     *
Benjamin Morel's avatar
Benjamin Morel committed
943
     * @return string The logical expression.
944 945 946
     */
    public function getNotExpression($expression)
    {
947
        return 'NOT(' . $expression . ')';
948 949 950
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
951
     * Returns the SQL that checks if an expression is null.
952
     *
Benjamin Morel's avatar
Benjamin Morel committed
953
     * @param string $expression The expression that should be compared to null.
954
     *
Benjamin Morel's avatar
Benjamin Morel committed
955
     * @return string The logical expression.
956 957 958 959 960 961 962
     */
    public function getIsNullExpression($expression)
    {
        return $expression . ' IS NULL';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
963
     * Returns the SQL that checks if an expression is not null.
964
     *
Benjamin Morel's avatar
Benjamin Morel committed
965
     * @param string $expression The expression that should be compared to null.
966
     *
Benjamin Morel's avatar
Benjamin Morel committed
967
     * @return string The logical expression.
968 969 970 971 972 973 974
     */
    public function getIsNotNullExpression($expression)
    {
        return $expression . ' IS NOT NULL';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
975
     * Returns the SQL that checks if an expression evaluates to a value between two values.
976 977 978 979 980 981 982
     *
     * The parameter $expression is checked if it is between $value1 and $value2.
     *
     * Note: There is a slight difference in the way BETWEEN works on some databases.
     * http://www.w3schools.com/sql/sql_between.asp. If you want complete database
     * independence you should avoid using between().
     *
Benjamin Morel's avatar
Benjamin Morel committed
983 984 985
     * @param string $expression The value to compare to.
     * @param string $value1     The lower value to compare with.
     * @param string $value2     The higher value to compare with.
986
     *
Benjamin Morel's avatar
Benjamin Morel committed
987
     * @return string The logical expression.
988 989 990
     */
    public function getBetweenExpression($expression, $value1, $value2)
    {
991
        return $expression . ' BETWEEN ' . $value1 . ' AND ' . $value2;
992 993
    }

Benjamin Morel's avatar
Benjamin Morel committed
994 995 996 997 998 999 1000
    /**
     * Returns the SQL to get the arccosine of a value.
     *
     * @param string $value
     *
     * @return string
     */
1001 1002 1003 1004 1005
    public function getAcosExpression($value)
    {
        return 'ACOS(' . $value . ')';
    }

Benjamin Morel's avatar
Benjamin Morel committed
1006 1007 1008 1009 1010 1011 1012
    /**
     * Returns the SQL to get the sine of a value.
     *
     * @param string $value
     *
     * @return string
     */
1013 1014 1015 1016 1017
    public function getSinExpression($value)
    {
        return 'SIN(' . $value . ')';
    }

Benjamin Morel's avatar
Benjamin Morel committed
1018 1019 1020 1021 1022
    /**
     * Returns the SQL to get the PI value.
     *
     * @return string
     */
1023 1024 1025 1026 1027
    public function getPiExpression()
    {
        return 'PI()';
    }

Benjamin Morel's avatar
Benjamin Morel committed
1028 1029 1030 1031 1032 1033 1034
    /**
     * Returns the SQL to get the cosine of a value.
     *
     * @param string $value
     *
     * @return string
     */
1035 1036 1037 1038
    public function getCosExpression($value)
    {
        return 'COS(' . $value . ')';
    }
1039

1040
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1041
     * Returns the SQL to calculate the difference in days between the two passed dates.
1042
     *
Benjamin Morel's avatar
Benjamin Morel committed
1043
     * Computes diff = date1 - date2.
1044 1045 1046
     *
     * @param string $date1
     * @param string $date2
1047
     *
1048
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
1049
     *
1050
     * @throws DBALException If not supported on this platform.
1051 1052 1053 1054
     */
    public function getDateDiffExpression($date1, $date2)
    {
        throw DBALException::notSupported(__METHOD__);
1055 1056
    }

1057 1058 1059
    /**
     * Returns the SQL to add the number of given seconds to a date.
     *
1060 1061
     * @param string $date
     * @param int    $seconds
1062 1063 1064
     *
     * @return string
     *
1065
     * @throws DBALException If not supported on this platform.
1066 1067 1068
     */
    public function getDateAddSecondsExpression($date, $seconds)
    {
1069
        return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, DateIntervalUnit::SECOND);
1070 1071 1072 1073 1074
    }

    /**
     * Returns the SQL to subtract the number of given seconds from a date.
     *
1075 1076
     * @param string $date
     * @param int    $seconds
1077 1078 1079
     *
     * @return string
     *
1080
     * @throws DBALException If not supported on this platform.
1081 1082 1083
     */
    public function getDateSubSecondsExpression($date, $seconds)
    {
1084
        return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, DateIntervalUnit::SECOND);
1085 1086 1087 1088 1089
    }

    /**
     * Returns the SQL to add the number of given minutes to a date.
     *
1090 1091
     * @param string $date
     * @param int    $minutes
1092 1093 1094
     *
     * @return string
     *
1095
     * @throws DBALException If not supported on this platform.
1096 1097 1098
     */
    public function getDateAddMinutesExpression($date, $minutes)
    {
1099
        return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, DateIntervalUnit::MINUTE);
1100 1101 1102 1103 1104
    }

    /**
     * Returns the SQL to subtract the number of given minutes from a date.
     *
1105 1106
     * @param string $date
     * @param int    $minutes
1107 1108 1109
     *
     * @return string
     *
1110
     * @throws DBALException If not supported on this platform.
1111 1112 1113
     */
    public function getDateSubMinutesExpression($date, $minutes)
    {
1114
        return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, DateIntervalUnit::MINUTE);
1115 1116
    }

1117 1118 1119
    /**
     * Returns the SQL to add the number of given hours to a date.
     *
1120 1121
     * @param string $date
     * @param int    $hours
1122 1123 1124
     *
     * @return string
     *
1125
     * @throws DBALException If not supported on this platform.
1126 1127 1128
     */
    public function getDateAddHourExpression($date, $hours)
    {
1129
        return $this->getDateArithmeticIntervalExpression($date, '+', $hours, DateIntervalUnit::HOUR);
1130 1131 1132 1133 1134
    }

    /**
     * Returns the SQL to subtract the number of given hours to a date.
     *
1135 1136
     * @param string $date
     * @param int    $hours
1137 1138 1139
     *
     * @return string
     *
1140
     * @throws DBALException If not supported on this platform.
1141 1142 1143
     */
    public function getDateSubHourExpression($date, $hours)
    {
1144
        return $this->getDateArithmeticIntervalExpression($date, '-', $hours, DateIntervalUnit::HOUR);
1145 1146 1147
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1148
     * Returns the SQL to add the number of given days to a date.
1149
     *
1150 1151
     * @param string $date
     * @param int    $days
1152
     *
1153
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
1154
     *
1155
     * @throws DBALException If not supported on this platform.
1156 1157 1158
     */
    public function getDateAddDaysExpression($date, $days)
    {
1159
        return $this->getDateArithmeticIntervalExpression($date, '+', $days, DateIntervalUnit::DAY);
1160 1161 1162
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1163
     * Returns the SQL to subtract the number of given days to a date.
1164
     *
1165 1166
     * @param string $date
     * @param int    $days
1167
     *
1168
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
1169
     *
1170
     * @throws DBALException If not supported on this platform.
1171 1172 1173
     */
    public function getDateSubDaysExpression($date, $days)
    {
1174
        return $this->getDateArithmeticIntervalExpression($date, '-', $days, DateIntervalUnit::DAY);
1175 1176 1177 1178 1179
    }

    /**
     * Returns the SQL to add the number of given weeks to a date.
     *
1180 1181
     * @param string $date
     * @param int    $weeks
1182 1183 1184
     *
     * @return string
     *
1185
     * @throws DBALException If not supported on this platform.
1186 1187 1188
     */
    public function getDateAddWeeksExpression($date, $weeks)
    {
1189
        return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, DateIntervalUnit::WEEK);
1190 1191 1192 1193 1194
    }

    /**
     * Returns the SQL to subtract the number of given weeks from a date.
     *
1195 1196
     * @param string $date
     * @param int    $weeks
1197 1198 1199
     *
     * @return string
     *
1200
     * @throws DBALException If not supported on this platform.
1201 1202 1203
     */
    public function getDateSubWeeksExpression($date, $weeks)
    {
1204
        return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, DateIntervalUnit::WEEK);
1205 1206 1207
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1208
     * Returns the SQL to add the number of given months to a date.
1209
     *
1210 1211
     * @param string $date
     * @param int    $months
1212
     *
1213
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
1214
     *
1215
     * @throws DBALException If not supported on this platform.
1216 1217 1218
     */
    public function getDateAddMonthExpression($date, $months)
    {
1219
        return $this->getDateArithmeticIntervalExpression($date, '+', $months, DateIntervalUnit::MONTH);
1220 1221 1222
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1223
     * Returns the SQL to subtract the number of given months to a date.
1224
     *
1225 1226
     * @param string $date
     * @param int    $months
1227
     *
1228
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
1229
     *
1230
     * @throws DBALException If not supported on this platform.
1231 1232
     */
    public function getDateSubMonthExpression($date, $months)
1233
    {
1234
        return $this->getDateArithmeticIntervalExpression($date, '-', $months, DateIntervalUnit::MONTH);
1235 1236 1237 1238 1239
    }

    /**
     * Returns the SQL to add the number of given quarters to a date.
     *
1240 1241
     * @param string $date
     * @param int    $quarters
1242 1243 1244
     *
     * @return string
     *
1245
     * @throws DBALException If not supported on this platform.
1246 1247 1248
     */
    public function getDateAddQuartersExpression($date, $quarters)
    {
1249
        return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, DateIntervalUnit::QUARTER);
1250 1251 1252 1253 1254
    }

    /**
     * Returns the SQL to subtract the number of given quarters from a date.
     *
1255 1256
     * @param string $date
     * @param int    $quarters
1257 1258 1259
     *
     * @return string
     *
1260
     * @throws DBALException If not supported on this platform.
1261 1262 1263
     */
    public function getDateSubQuartersExpression($date, $quarters)
    {
1264
        return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, DateIntervalUnit::QUARTER);
1265 1266 1267 1268 1269
    }

    /**
     * Returns the SQL to add the number of given years to a date.
     *
1270 1271
     * @param string $date
     * @param int    $years
1272 1273 1274
     *
     * @return string
     *
1275
     * @throws DBALException If not supported on this platform.
1276 1277 1278
     */
    public function getDateAddYearsExpression($date, $years)
    {
1279
        return $this->getDateArithmeticIntervalExpression($date, '+', $years, DateIntervalUnit::YEAR);
1280 1281 1282 1283 1284
    }

    /**
     * Returns the SQL to subtract the number of given years from a date.
     *
1285 1286
     * @param string $date
     * @param int    $years
1287 1288 1289
     *
     * @return string
     *
1290
     * @throws DBALException If not supported on this platform.
1291 1292 1293
     */
    public function getDateSubYearsExpression($date, $years)
    {
1294
        return $this->getDateArithmeticIntervalExpression($date, '-', $years, DateIntervalUnit::YEAR);
1295 1296 1297 1298 1299
    }

    /**
     * Returns the SQL for a date arithmetic expression.
     *
1300 1301 1302 1303 1304
     * @param string $date     The column or literal representing a date to perform the arithmetic operation on.
     * @param string $operator The arithmetic operator (+ or -).
     * @param int    $interval The interval that shall be calculated into the date.
     * @param string $unit     The unit of the interval that shall be calculated into the date.
     *                         One of the DATE_INTERVAL_UNIT_* constants.
1305 1306 1307
     *
     * @return string
     *
1308
     * @throws DBALException If not supported on this platform.
1309 1310
     */
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
1311 1312 1313 1314
    {
        throw DBALException::notSupported(__METHOD__);
    }

1315
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1316
     * Returns the SQL bit AND comparison expression.
1317
     *
Benjamin Morel's avatar
Benjamin Morel committed
1318 1319
     * @param string $value1
     * @param string $value2
1320
     *
Benjamin Morel's avatar
Benjamin Morel committed
1321
     * @return string
1322 1323 1324 1325 1326
     */
    public function getBitAndComparisonExpression($value1, $value2)
    {
        return '(' . $value1 . ' & ' . $value2 . ')';
    }
Fabio B. Silva's avatar
Fabio B. Silva committed
1327

1328
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1329
     * Returns the SQL bit OR comparison expression.
1330
     *
Benjamin Morel's avatar
Benjamin Morel committed
1331 1332
     * @param string $value1
     * @param string $value2
1333
     *
Benjamin Morel's avatar
Benjamin Morel committed
1334
     * @return string
1335 1336 1337 1338 1339 1340
     */
    public function getBitOrComparisonExpression($value1, $value2)
    {
        return '(' . $value1 . ' | ' . $value2 . ')';
    }

Benjamin Morel's avatar
Benjamin Morel committed
1341 1342
    /**
     * Returns the FOR UPDATE expression.
1343
     *
Benjamin Morel's avatar
Benjamin Morel committed
1344 1345
     * @return string
     */
1346
    public function getForUpdateSQL()
1347 1348 1349
    {
        return 'FOR UPDATE';
    }
1350

1351 1352 1353
    /**
     * Honors that some SQL vendors such as MsSql use table hints for locking instead of the ANSI SQL FOR UPDATE specification.
     *
1354 1355 1356
     * @param string   $fromClause The FROM clause to append the hint for the given lock mode to.
     * @param int|null $lockMode   One of the Doctrine\DBAL\LockMode::* constants. If null is given, nothing will
     *                             be appended to the FROM clause.
1357
     *
1358 1359 1360 1361 1362 1363 1364 1365
     * @return string
     */
    public function appendLockHint($fromClause, $lockMode)
    {
        return $fromClause;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1366
     * Returns the SQL snippet to append to any SELECT statement which locks rows in shared read lock.
1367
     *
1368
     * This defaults to the ANSI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database
1369 1370 1371 1372 1373 1374 1375 1376 1377 1378
     * vendors allow to lighten this constraint up to be a real read lock.
     *
     * @return string
     */
    public function getReadLockSQL()
    {
        return $this->getForUpdateSQL();
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1379
     * Returns the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows.
1380
     *
1381
     * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ANSI SQL standard.
1382 1383 1384 1385 1386 1387 1388 1389
     *
     * @return string
     */
    public function getWriteLockSQL()
    {
        return $this->getForUpdateSQL();
    }

1390
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1391
     * Returns the SQL snippet to drop an existing database.
1392
     *
Benjamin Morel's avatar
Benjamin Morel committed
1393
     * @param string $database The name of the database that should be dropped.
1394 1395 1396
     *
     * @return string
     */
1397
    public function getDropDatabaseSQL($database)
1398 1399 1400
    {
        return 'DROP DATABASE ' . $database;
    }
1401

1402
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1403
     * Returns the SQL snippet to drop an existing table.
1404
     *
1405
     * @param Table|string $table
1406
     *
1407
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
1408
     *
1409
     * @throws InvalidArgumentException
1410
     */
1411
    public function getDropTableSQL($table)
1412
    {
1413 1414
        $tableArg = $table;

1415
        if ($table instanceof Table) {
1416
            $table = $table->getQuotedName($this);
1417 1418 1419
        }

        if (! is_string($table)) {
1420
            throw new InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
1421 1422
        }

1423
        if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaDropTable)) {
1424
            $eventArgs = new SchemaDropTableEventArgs($tableArg, $this);
1425 1426 1427
            $this->_eventManager->dispatchEvent(Events::onSchemaDropTable, $eventArgs);

            if ($eventArgs->isDefaultPrevented()) {
Sergei Morozov's avatar
Sergei Morozov committed
1428 1429 1430 1431 1432 1433 1434
                $sql = $eventArgs->getSql();

                if ($sql === null) {
                    throw new UnexpectedValueException('Default implementation of DROP TABLE was overridden with NULL');
                }

                return $sql;
1435 1436
            }
        }
1437

1438 1439
        return 'DROP TABLE ' . $table;
    }
1440

1441
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1442
     * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction.
1443
     *
1444
     * @param Table|string $table
1445
     *
1446 1447 1448 1449 1450 1451 1452
     * @return string
     */
    public function getDropTemporaryTableSQL($table)
    {
        return $this->getDropTableSQL($table);
    }

1453
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1454
     * Returns the SQL to drop an index from a table.
1455
     *
1456 1457
     * @param Index|string $index
     * @param Table|string $table
1458
     *
1459
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
1460
     *
1461
     * @throws InvalidArgumentException
1462
     */
1463
    public function getDropIndexSQL($index, $table = null)
1464
    {
1465
        if ($index instanceof Index) {
1466
            $index = $index->getQuotedName($this);
1467 1468
        } elseif (! is_string($index)) {
            throw new InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
1469 1470 1471
        }

        return 'DROP INDEX ' . $index;
1472
    }
1473

1474
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1475
     * Returns the SQL to drop a constraint.
1476
     *
1477 1478
     * @param Constraint|string $constraint
     * @param Table|string      $table
1479
     *
1480 1481
     * @return string
     */
1482
    public function getDropConstraintSQL($constraint, $table)
1483
    {
1484 1485
        if (! $constraint instanceof Constraint) {
            $constraint = new Identifier($constraint);
1486 1487
        }

1488 1489
        if (! $table instanceof Table) {
            $table = new Identifier($table);
1490 1491
        }

1492
        $constraint = $constraint->getQuotedName($this);
1493
        $table      = $table->getQuotedName($this);
1494

1495
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint;
1496
    }
1497

1498
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1499
     * Returns the SQL to drop a foreign key.
1500
     *
1501 1502
     * @param ForeignKeyConstraint|string $foreignKey
     * @param Table|string                $table
1503
     *
1504 1505
     * @return string
     */
1506
    public function getDropForeignKeySQL($foreignKey, $table)
1507
    {
1508 1509
        if (! $foreignKey instanceof ForeignKeyConstraint) {
            $foreignKey = new Identifier($foreignKey);
1510 1511
        }

1512 1513
        if (! $table instanceof Table) {
            $table = new Identifier($table);
1514 1515
        }

1516
        $foreignKey = $foreignKey->getQuotedName($this);
1517
        $table      = $table->getQuotedName($this);
1518

1519
        return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey;
1520
    }
1521

1522
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1523
     * Returns the SQL statement(s) to create a table with the specified name, columns and constraints
1524
     * on this platform.
1525
     *
1526
     * @param int $createFlags
1527
     *
1528
     * @return string[] The sequence of SQL statements.
Benjamin Morel's avatar
Benjamin Morel committed
1529
     *
1530 1531
     * @throws DBALException
     * @throws InvalidArgumentException
1532
     */
1533
    public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES)
1534
    {
1535 1536
        if (! is_int($createFlags)) {
            throw new InvalidArgumentException('Second argument of AbstractPlatform::getCreateTableSQL() has to be integer.');
1537 1538
        }

1539
        if (count($table->getColumns()) === 0) {
1540 1541 1542
            throw DBALException::noColumnsSpecifiedForTable($table->getName());
        }

1543 1544
        $tableName                    = $table->getQuotedName($this);
        $options                      = $table->getOptions();
1545
        $options['uniqueConstraints'] = [];
1546 1547
        $options['indexes']           = [];
        $options['primary']           = [];
1548

1549
        if (($createFlags&self::CREATE_INDEXES) > 0) {
1550
            foreach ($table->getIndexes() as $index) {
1551
                /** @var $index Index */
1552
                if ($index->isPrimary()) {
Steve Müller's avatar
Steve Müller committed
1553
                    $options['primary']       = $index->getQuotedColumns($this);
1554
                    $options['primary_index'] = $index;
1555
                } else {
1556
                    $options['indexes'][$index->getQuotedName($this)] = $index;
1557
                }
1558 1559
            }
        }
1560

1561
        $columnSql = [];
1562
        $columns   = [];
1563

1564
        foreach ($table->getColumns() as $column) {
1565
            if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn)) {
1566 1567 1568 1569 1570 1571 1572 1573 1574 1575
                $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this);
                $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs);

                $columnSql = array_merge($columnSql, $eventArgs->getSql());

                if ($eventArgs->isDefaultPrevented()) {
                    continue;
                }
            }

1576 1577 1578
            $columnData            = $column->toArray();
            $columnData['name']    = $column->getQuotedName($this);
            $columnData['version'] = $column->hasPlatformOption('version') ? $column->getPlatformOption('version') : false;
1579
            $columnData['comment'] = $this->getColumnComment($column);
1580

1581
            if ($columnData['type'] instanceof Types\StringType && $columnData['length'] === null) {
1582 1583
                $columnData['length'] = 255;
            }
1584 1585

            if (in_array($column->getName(), $options['primary'])) {
1586 1587 1588 1589 1590 1591
                $columnData['primary'] = true;
            }

            $columns[$columnData['name']] = $columnData;
        }

1592
        if (($createFlags&self::CREATE_FOREIGNKEYS) > 0) {
1593
            $options['foreignKeys'] = [];
1594
            foreach ($table->getForeignKeys() as $fkConstraint) {
1595 1596 1597 1598
                $options['foreignKeys'][] = $fkConstraint;
            }
        }

1599
        if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) {
1600 1601
            $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this);
            $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs);
1602

Jan Sorgalla's avatar
Jan Sorgalla committed
1603 1604 1605
            if ($eventArgs->isDefaultPrevented()) {
                return array_merge($eventArgs->getSql(), $columnSql);
            }
1606
        }
1607

1608 1609
        $sql = $this->_getCreateTableSQL($tableName, $columns, $options);
        if ($this->supportsCommentOnStatement()) {
1610 1611 1612
            if ($table->hasOption('comment')) {
                $sql[] = $this->getCommentOnTableSQL($tableName, $table->getOption('comment'));
            }
1613
            foreach ($table->getColumns() as $column) {
1614 1615
                $comment = $this->getColumnComment($column);

1616 1617
                if ($comment === null || $comment === '') {
                    continue;
1618
                }
1619 1620

                $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment);
1621 1622
            }
        }
1623

Jan Sorgalla's avatar
Jan Sorgalla committed
1624
        return array_merge($sql, $columnSql);
1625 1626
    }

1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637
    protected function getCommentOnTableSQL(string $tableName, ?string $comment) : string
    {
        $tableName = new Identifier($tableName);

        return sprintf(
            'COMMENT ON TABLE %s IS %s',
            $tableName->getQuotedName($this),
            $this->quoteStringLiteral((string) $comment)
        );
    }

Benjamin Morel's avatar
Benjamin Morel committed
1638
    /**
Sergei Morozov's avatar
Sergei Morozov committed
1639 1640 1641
     * @param string      $tableName
     * @param string      $columnName
     * @param string|null $comment
Benjamin Morel's avatar
Benjamin Morel committed
1642 1643 1644
     *
     * @return string
     */
1645 1646
    public function getCommentOnColumnSQL($tableName, $columnName, $comment)
    {
1647
        $tableName  = new Identifier($tableName);
1648
        $columnName = new Identifier($columnName);
1649

1650 1651 1652 1653
        return sprintf(
            'COMMENT ON COLUMN %s.%s IS %s',
            $tableName->getQuotedName($this),
            $columnName->getQuotedName($this),
Sergei Morozov's avatar
Sergei Morozov committed
1654
            $this->quoteStringLiteral((string) $comment)
1655
        );
1656 1657
    }

1658 1659 1660 1661 1662 1663
    /**
     * Returns the SQL to create inline comment on a column.
     *
     * @param string $comment
     *
     * @return string
1664
     *
1665
     * @throws DBALException If not supported on this platform.
1666 1667 1668
     */
    public function getInlineColumnCommentSQL($comment)
    {
1669 1670 1671 1672
        if (! $this->supportsInlineColumnComments()) {
            throw DBALException::notSupported(__METHOD__);
        }

1673
        return 'COMMENT ' . $this->quoteStringLiteral($comment);
1674 1675
    }

1676
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1677
     * Returns the SQL used to create a table.
1678
     *
1679 1680 1681
     * @param string    $tableName
     * @param mixed[][] $columns
     * @param mixed[]   $options
1682
     *
1683
     * @return string[]
1684
     */
1685
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
1686
    {
1687
        $columnListSql = $this->getColumnDeclarationListSQL($columns);
1688

1689
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
1690
            foreach ($options['uniqueConstraints'] as $name => $definition) {
1691
                $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
1692 1693
            }
        }
1694

1695
        if (isset($options['primary']) && ! empty($options['primary'])) {
1696
            $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')';
1697 1698 1699
        }

        if (isset($options['indexes']) && ! empty($options['indexes'])) {
Steve Müller's avatar
Steve Müller committed
1700
            foreach ($options['indexes'] as $index => $definition) {
1701
                $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition);
1702 1703 1704
            }
        }

1705
        $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;
1706

1707
        $check = $this->getCheckDeclarationSQL($columns);
1708
        if (! empty($check)) {
1709
            $query .= ', ' . $check;
1710
        }
1711 1712 1713 1714 1715
        $query .= ')';

        $sql[] = $query;

        if (isset($options['foreignKeys'])) {
1716
            foreach ((array) $options['foreignKeys'] as $definition) {
1717
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
1718 1719
            }
        }
1720

1721 1722
        return $sql;
    }
1723

Benjamin Morel's avatar
Benjamin Morel committed
1724 1725 1726
    /**
     * @return string
     */
1727
    public function getCreateTemporaryTableSnippetSQL()
1728
    {
1729
        return 'CREATE TEMPORARY TABLE';
1730
    }
1731

1732
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1733
     * Returns the SQL to create a sequence on this platform.
1734
     *
1735 1736
     * @return string
     *
1737
     * @throws DBALException If not supported on this platform.
1738
     */
1739
    public function getCreateSequenceSQL(Sequence $sequence)
1740
    {
1741
        throw DBALException::notSupported(__METHOD__);
1742
    }
1743

1744
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1745
     * Returns the SQL to change a sequence on this platform.
1746
     *
1747
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
1748
     *
1749
     * @throws DBALException If not supported on this platform.
1750
     */
1751
    public function getAlterSequenceSQL(Sequence $sequence)
1752 1753 1754
    {
        throw DBALException::notSupported(__METHOD__);
    }
1755

1756
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1757
     * Returns the SQL to create a constraint on a table on this platform.
1758
     *
1759
     * @param Table|string $table
1760
     *
1761
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
1762
     *
1763
     * @throws InvalidArgumentException
1764
     */
1765
    public function getCreateConstraintSQL(Constraint $constraint, $table)
1766
    {
1767
        if ($table instanceof Table) {
1768
            $table = $table->getQuotedName($this);
1769 1770
        }

1771
        $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this);
1772

1773
        $columnList = '(' . implode(', ', $constraint->getQuotedColumns($this)) . ')';
1774 1775

        $referencesClause = '';
1776
        if ($constraint instanceof Index) {
Steve Müller's avatar
Steve Müller committed
1777
            if ($constraint->isPrimary()) {
1778 1779 1780 1781
                $query .= ' PRIMARY KEY';
            } elseif ($constraint->isUnique()) {
                $query .= ' UNIQUE';
            } else {
1782
                throw new InvalidArgumentException(
1783
                    'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().'
1784 1785
                );
            }
Steve Müller's avatar
Steve Müller committed
1786
        } elseif ($constraint instanceof ForeignKeyConstraint) {
1787 1788
            $query .= ' FOREIGN KEY';

1789
            $referencesClause = ' REFERENCES ' . $constraint->getQuotedForeignTableName($this) .
1790
                ' (' . implode(', ', $constraint->getQuotedForeignColumns($this)) . ')';
1791
        }
1792
        $query .= ' ' . $columnList . $referencesClause;
1793 1794 1795

        return $query;
    }
1796

1797
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1798
     * Returns the SQL to create an index on a table on this platform.
1799
     *
1800
     * @param Table|string $table The name of the table on which the index is to be created.
1801
     *
1802
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
1803
     *
1804
     * @throws InvalidArgumentException
1805
     */
1806
    public function getCreateIndexSQL(Index $index, $table)
1807
    {
1808
        if ($table instanceof Table) {
1809
            $table = $table->getQuotedName($this);
1810
        }
1811
        $name    = $index->getQuotedName($this);
1812
        $columns = $index->getColumns();
1813

1814 1815
        if (count($columns) === 0) {
            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
1816
        }
1817

1818 1819
        if ($index->isPrimary()) {
            return $this->getCreatePrimaryKeySQL($index, $table);
1820 1821
        }

1822
        $query  = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table;
1823
        $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index);
1824

1825 1826
        return $query;
    }
1827

1828 1829 1830 1831 1832 1833 1834
    /**
     * Adds condition for partial index.
     *
     * @return string
     */
    protected function getPartialIndexSQL(Index $index)
    {
1835
        if ($this->supportsPartialIndexes() && $index->hasOption('where')) {
1836
            return ' WHERE ' . $index->getOption('where');
1837
        }
1838 1839

        return '';
1840 1841
    }

1842
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1843
     * Adds additional flags for index generation.
1844 1845 1846 1847 1848
     *
     * @return string
     */
    protected function getCreateIndexSQLFlags(Index $index)
    {
1849
        return $index->isUnique() ? 'UNIQUE ' : '';
1850 1851
    }

1852
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1853
     * Returns the SQL to create an unnamed primary key constraint.
1854
     *
1855
     * @param Table|string $table
1856
     *
1857 1858 1859 1860
     * @return string
     */
    public function getCreatePrimaryKeySQL(Index $index, $table)
    {
Sergei Morozov's avatar
Sergei Morozov committed
1861 1862 1863 1864
        if ($table instanceof Table) {
            $table = $table->getQuotedName($this);
        }

1865
        return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')';
1866
    }
1867

1868 1869 1870 1871 1872 1873
    /**
     * Returns the SQL to create a named schema.
     *
     * @param string $schemaName
     *
     * @return string
1874 1875
     *
     * @throws DBALException If not supported on this platform.
1876 1877 1878 1879 1880 1881
     */
    public function getCreateSchemaSQL($schemaName)
    {
        throw DBALException::notSupported(__METHOD__);
    }

1882
    /**
1883
     * Quotes a string so that it can be safely used as a table or column name,
1884
     * even if it is a reserved word of the platform. This also detects identifier
1885
     * chains separated by dot and quotes them independently.
1886
     *
1887
     * NOTE: Just because you CAN use quoted identifiers doesn't mean
Benjamin Morel's avatar
Benjamin Morel committed
1888
     * you SHOULD use them. In general, they end up causing way more
1889 1890
     * problems than they solve.
     *
Benjamin Morel's avatar
Benjamin Morel committed
1891
     * @param string $str The identifier name to be quoted.
1892
     *
Benjamin Morel's avatar
Benjamin Morel committed
1893
     * @return string The quoted identifier string.
1894 1895
     */
    public function quoteIdentifier($str)
1896
    {
1897 1898
        if (strpos($str, '.') !== false) {
            $parts = array_map([$this, 'quoteSingleIdentifier'], explode('.', $str));
1899

1900
            return implode('.', $parts);
1901 1902 1903 1904 1905 1906
        }

        return $this->quoteSingleIdentifier($str);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1907
     * Quotes a single identifier (no dot chain separation).
1908
     *
Benjamin Morel's avatar
Benjamin Morel committed
1909
     * @param string $str The identifier name to be quoted.
1910
     *
Benjamin Morel's avatar
Benjamin Morel committed
1911
     * @return string The quoted identifier string.
1912 1913
     */
    public function quoteSingleIdentifier($str)
1914 1915 1916
    {
        $c = $this->getIdentifierQuoteCharacter();

1917
        return $c . str_replace($c, $c . $c, $str) . $c;
1918
    }
1919

1920
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1921
     * Returns the SQL to create a new foreign key.
1922
     *
1923 1924
     * @param ForeignKeyConstraint $foreignKey The foreign key constraint.
     * @param Table|string         $table      The name of the table on which the foreign key is to be created.
1925
     *
1926 1927
     * @return string
     */
1928
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
1929
    {
1930
        if ($table instanceof Table) {
1931
            $table = $table->getQuotedName($this);
1932 1933
        }

1934
        return 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey);
1935
    }
1936

1937
    /**
Benjamin Morel's avatar
Benjamin Morel committed
1938
     * Gets the SQL statements for altering an existing table.
1939
     *
Benjamin Morel's avatar
Benjamin Morel committed
1940
     * This method returns an array of SQL statements, since some platforms need several statements.
1941
     *
1942
     * @return string[]
Benjamin Morel's avatar
Benjamin Morel committed
1943
     *
1944
     * @throws DBALException If not supported on this platform.
1945
     */
1946
    public function getAlterTableSQL(TableDiff $diff)
1947
    {
1948
        throw DBALException::notSupported(__METHOD__);
1949
    }
1950

1951
    /**
1952
     * @param mixed[] $columnSql
1953
     *
1954
     * @return bool
1955 1956 1957
     */
    protected function onSchemaAlterTableAddColumn(Column $column, TableDiff $diff, &$columnSql)
    {
1958
        if ($this->_eventManager === null) {
1959 1960 1961
            return false;
        }

1962
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableAddColumn)) {
1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974
            return false;
        }

        $eventArgs = new SchemaAlterTableAddColumnEventArgs($column, $diff, $this);
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableAddColumn, $eventArgs);

        $columnSql = array_merge($columnSql, $eventArgs->getSql());

        return $eventArgs->isDefaultPrevented();
    }

    /**
1975
     * @param string[] $columnSql
1976
     *
1977
     * @return bool
1978 1979 1980
     */
    protected function onSchemaAlterTableRemoveColumn(Column $column, TableDiff $diff, &$columnSql)
    {
1981
        if ($this->_eventManager === null) {
1982 1983 1984
            return false;
        }

1985
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRemoveColumn)) {
1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997
            return false;
        }

        $eventArgs = new SchemaAlterTableRemoveColumnEventArgs($column, $diff, $this);
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRemoveColumn, $eventArgs);

        $columnSql = array_merge($columnSql, $eventArgs->getSql());

        return $eventArgs->isDefaultPrevented();
    }

    /**
1998
     * @param string[] $columnSql
1999
     *
2000
     * @return bool
2001 2002 2003
     */
    protected function onSchemaAlterTableChangeColumn(ColumnDiff $columnDiff, TableDiff $diff, &$columnSql)
    {
2004
        if ($this->_eventManager === null) {
2005 2006 2007
            return false;
        }

2008
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) {
2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
            return false;
        }

        $eventArgs = new SchemaAlterTableChangeColumnEventArgs($columnDiff, $diff, $this);
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableChangeColumn, $eventArgs);

        $columnSql = array_merge($columnSql, $eventArgs->getSql());

        return $eventArgs->isDefaultPrevented();
    }

    /**
2021 2022
     * @param string   $oldColumnName
     * @param string[] $columnSql
2023
     *
2024
     * @return bool
2025 2026 2027
     */
    protected function onSchemaAlterTableRenameColumn($oldColumnName, Column $column, TableDiff $diff, &$columnSql)
    {
2028
        if ($this->_eventManager === null) {
2029 2030 2031
            return false;
        }

2032
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRenameColumn)) {
2033 2034 2035 2036 2037 2038 2039 2040 2041 2042
            return false;
        }

        $eventArgs = new SchemaAlterTableRenameColumnEventArgs($oldColumnName, $column, $diff, $this);
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRenameColumn, $eventArgs);

        $columnSql = array_merge($columnSql, $eventArgs->getSql());

        return $eventArgs->isDefaultPrevented();
    }
Christophe Coevoet's avatar
Christophe Coevoet committed
2043

2044
    /**
2045
     * @param string[] $sql
2046
     *
2047
     * @return bool
2048 2049 2050
     */
    protected function onSchemaAlterTable(TableDiff $diff, &$sql)
    {
2051
        if ($this->_eventManager === null) {
2052 2053 2054
            return false;
        }

2055
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTable)) {
2056 2057 2058 2059 2060 2061 2062 2063 2064 2065
            return false;
        }

        $eventArgs = new SchemaAlterTableEventArgs($diff, $this);
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTable, $eventArgs);

        $sql = array_merge($sql, $eventArgs->getSql());

        return $eventArgs->isDefaultPrevented();
    }
2066

Benjamin Morel's avatar
Benjamin Morel committed
2067
    /**
2068
     * @return string[]
Benjamin Morel's avatar
Benjamin Morel committed
2069
     */
2070 2071
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
    {
2072
        $tableName = $diff->getName($this)->getQuotedName($this);
2073

2074
        $sql = [];
2075
        if ($this->supportsForeignKeyConstraints()) {
2076
            foreach ($diff->removedForeignKeys as $foreignKey) {
2077 2078
                $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName);
            }
2079
            foreach ($diff->changedForeignKeys as $foreignKey) {
2080 2081 2082 2083
                $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName);
            }
        }

2084
        foreach ($diff->removedIndexes as $index) {
2085 2086
            $sql[] = $this->getDropIndexSQL($index, $tableName);
        }
2087
        foreach ($diff->changedIndexes as $index) {
2088 2089 2090 2091 2092
            $sql[] = $this->getDropIndexSQL($index, $tableName);
        }

        return $sql;
    }
2093

Benjamin Morel's avatar
Benjamin Morel committed
2094
    /**
2095
     * @return string[]
Benjamin Morel's avatar
Benjamin Morel committed
2096
     */
2097
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
2098
    {
Sergei Morozov's avatar
Sergei Morozov committed
2099 2100
        $sql     = [];
        $newName = $diff->getNewName();
2101

Sergei Morozov's avatar
Sergei Morozov committed
2102 2103 2104 2105 2106
        if ($newName !== false) {
            $tableName = $newName->getQuotedName($this);
        } else {
            $tableName = $diff->getName($this)->getQuotedName($this);
        }
2107

2108
        if ($this->supportsForeignKeyConstraints()) {
2109
            foreach ($diff->addedForeignKeys as $foreignKey) {
2110
                $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
2111
            }
2112

2113
            foreach ($diff->changedForeignKeys as $foreignKey) {
2114
                $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
2115 2116 2117
            }
        }

2118
        foreach ($diff->addedIndexes as $index) {
2119
            $sql[] = $this->getCreateIndexSQL($index, $tableName);
2120
        }
2121

2122
        foreach ($diff->changedIndexes as $index) {
2123
            $sql[] = $this->getCreateIndexSQL($index, $tableName);
2124 2125
        }

2126 2127 2128 2129 2130 2131 2132 2133
        foreach ($diff->renamedIndexes as $oldIndexName => $index) {
            $oldIndexName = new Identifier($oldIndexName);
            $sql          = array_merge(
                $sql,
                $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableName)
            );
        }

2134 2135
        return $sql;
    }
2136

2137 2138 2139
    /**
     * Returns the SQL for renaming an index on a table.
     *
2140 2141 2142
     * @param string $oldIndexName The name of the index to rename from.
     * @param Index  $index        The definition of the index to rename to.
     * @param string $tableName    The table to rename the given index on.
2143
     *
2144
     * @return string[] The sequence of SQL statements for renaming the given index.
2145 2146 2147
     */
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
    {
2148
        return [
2149
            $this->getDropIndexSQL($oldIndexName, $tableName),
2150
            $this->getCreateIndexSQL($index, $tableName),
2151
        ];
2152 2153
    }

2154 2155 2156
    /**
     * Common code for alter table statement generation that updates the changed Index and Foreign Key definitions.
     *
2157 2158
     * @deprecated
     *
2159
     * @return string[]
2160 2161 2162 2163 2164
     */
    protected function _getAlterTableIndexForeignKeySQL(TableDiff $diff)
    {
        return array_merge($this->getPreAlterTableIndexForeignKeySQL($diff), $this->getPostAlterTableIndexForeignKeySQL($diff));
    }
2165

2166
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2167
     * Gets declaration of a number of fields in bulk.
2168
     *
2169 2170 2171 2172 2173
     * @param mixed[][] $fields A multidimensional associative array.
     *                          The first dimension determines the field name, while the second
     *                          dimension is keyed with the name of the properties
     *                          of the field being declared as array indexes. Currently, the types
     *                          of supported field properties are as follows:
2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194
     *
     *      length
     *          Integer value that determines the maximum length of the text
     *          field. If this argument is missing the field should be
     *          declared to have the longest length allowed by the DBMS.
     *
     *      default
     *          Text value to be used as default for this field.
     *
     *      notnull
     *          Boolean flag that indicates whether this field is constrained
     *          to not be set to null.
     *      charset
     *          Text value with the default CHARACTER SET for this field.
     *      collation
     *          Text value with the default COLLATION for this field.
     *      unique
     *          unique constraint
     *
     * @return string
     */
2195
    public function getColumnDeclarationListSQL(array $fields)
2196
    {
2197
        $queryFields = [];
2198

2199
        foreach ($fields as $fieldName => $field) {
2200
            $queryFields[] = $this->getColumnDeclarationSQL($fieldName, $field);
2201
        }
2202

2203 2204 2205 2206
        return implode(', ', $queryFields);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2207
     * Obtains DBMS specific SQL code portion needed to declare a generic type
2208 2209
     * field to be used in statements like CREATE TABLE.
     *
2210 2211 2212 2213
     * @param string  $name  The name the field to be declared.
     * @param mixed[] $field An associative array with the name of the properties
     *                       of the field being declared as array indexes. Currently, the types
     *                       of supported field properties are as follows:
2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233
     *
     *      length
     *          Integer value that determines the maximum length of the text
     *          field. If this argument is missing the field should be
     *          declared to have the longest length allowed by the DBMS.
     *
     *      default
     *          Text value to be used as default for this field.
     *
     *      notnull
     *          Boolean flag that indicates whether this field is constrained
     *          to not be set to null.
     *      charset
     *          Text value with the default CHARACTER SET for this field.
     *      collation
     *          Text value with the default COLLATION for this field.
     *      unique
     *          unique constraint
     *      check
     *          column check constraint
2234 2235
     *      columnDefinition
     *          a string that defines the complete column
2236
     *
Benjamin Morel's avatar
Benjamin Morel committed
2237
     * @return string DBMS specific SQL code portion that should be used to declare the column.
2238
     */
2239
    public function getColumnDeclarationSQL($name, array $field)
2240
    {
2241
        if (isset($field['columnDefinition'])) {
2242
            $columnDef = $this->getCustomTypeDeclarationSQL($field);
2243
        } else {
2244
            $default = $this->getDefaultValueDeclarationSQL($field);
2245

2246
            $charset = isset($field['charset']) && $field['charset'] ?
2247
                ' ' . $this->getColumnCharsetDeclarationSQL($field['charset']) : '';
2248

2249
            $collation = isset($field['collation']) && $field['collation'] ?
2250
                ' ' . $this->getColumnCollationDeclarationSQL($field['collation']) : '';
2251

2252
            $notnull = isset($field['notnull']) && $field['notnull'] ? ' NOT NULL' : '';
2253

2254
            $unique = isset($field['unique']) && $field['unique'] ?
2255
                ' ' . $this->getUniqueFieldDeclarationSQL() : '';
2256

2257
            $check = isset($field['check']) && $field['check'] ?
2258
                ' ' . $field['check'] : '';
2259

2260
            $typeDecl  = $field['type']->getSQLDeclaration($field, $this);
2261
            $columnDef = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation;
2262

2263 2264 2265
            if ($this->supportsInlineColumnComments() && isset($field['comment']) && $field['comment'] !== '') {
                $columnDef .= ' ' . $this->getInlineColumnCommentSQL($field['comment']);
            }
2266 2267
        }

2268
        return $name . ' ' . $columnDef;
2269
    }
2270

2271
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2272
     * Returns the SQL snippet that declares a floating point column of arbitrary precision.
2273
     *
2274
     * @param mixed[] $columnDef
2275
     *
2276 2277
     * @return string
     */
2278
    public function getDecimalTypeDeclarationSQL(array $columnDef)
2279
    {
2280
        $columnDef['precision'] = ! isset($columnDef['precision']) || empty($columnDef['precision'])
2281
            ? 10 : $columnDef['precision'];
2282
        $columnDef['scale']     = ! isset($columnDef['scale']) || empty($columnDef['scale'])
2283
            ? 0 : $columnDef['scale'];
2284

2285 2286
        return 'NUMERIC(' . $columnDef['precision'] . ', ' . $columnDef['scale'] . ')';
    }
2287 2288

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2289
     * Obtains DBMS specific SQL code portion needed to set a default value
2290 2291
     * declaration to be used in statements like CREATE TABLE.
     *
2292
     * @param mixed[] $field The field definition array.
2293
     *
Benjamin Morel's avatar
Benjamin Morel committed
2294
     * @return string DBMS specific SQL code portion needed to set a default value.
2295
     */
2296
    public function getDefaultValueDeclarationSQL($field)
2297
    {
2298
        if (! isset($field['default'])) {
2299 2300 2301 2302 2303
            return empty($field['notnull']) ? ' DEFAULT NULL' : '';
        }

        $default = $field['default'];

2304
        if (! isset($field['type'])) {
2305
            return " DEFAULT '" . $default . "'";
2306 2307 2308 2309 2310
        }

        $type = $field['type'];

        if ($type instanceof Types\PhpIntegerMappingType) {
2311
            return ' DEFAULT ' . $default;
2312 2313 2314
        }

        if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) {
2315
            return ' DEFAULT ' . $this->getCurrentTimestampSQL();
2316 2317 2318
        }

        if ($type instanceof Types\TimeType && $default === $this->getCurrentTimeSQL()) {
2319
            return ' DEFAULT ' . $this->getCurrentTimeSQL();
2320 2321 2322
        }

        if ($type instanceof Types\DateType && $default === $this->getCurrentDateSQL()) {
2323
            return ' DEFAULT ' . $this->getCurrentDateSQL();
2324 2325 2326 2327
        }

        if ($type instanceof Types\BooleanType) {
            return " DEFAULT '" . $this->convertBooleans($default) . "'";
2328
        }
Benjamin Morel's avatar
Benjamin Morel committed
2329

2330
        return ' DEFAULT ' . $this->quoteStringLiteral($default);
2331 2332 2333
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2334
     * Obtains DBMS specific SQL code portion needed to set a CHECK constraint
2335 2336
     * declaration to be used in statements like CREATE TABLE.
     *
Sergei Morozov's avatar
Sergei Morozov committed
2337
     * @param string[]|mixed[][] $definition The check definition.
2338
     *
Benjamin Morel's avatar
Benjamin Morel committed
2339
     * @return string DBMS specific SQL code portion needed to set a CHECK constraint.
2340
     */
2341
    public function getCheckDeclarationSQL(array $definition)
2342
    {
2343
        $constraints = [];
2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359
        foreach ($definition as $field => $def) {
            if (is_string($def)) {
                $constraints[] = 'CHECK (' . $def . ')';
            } else {
                if (isset($def['min'])) {
                    $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')';
                }

                if (isset($def['max'])) {
                    $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')';
                }
            }
        }

        return implode(', ', $constraints);
    }
2360

2361
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2362
     * Obtains DBMS specific SQL code portion needed to set a unique
2363 2364
     * constraint declaration to be used in statements like CREATE TABLE.
     *
2365 2366
     * @param string $name  The name of the unique constraint.
     * @param Index  $index The index definition.
2367
     *
Benjamin Morel's avatar
Benjamin Morel committed
2368 2369
     * @return string DBMS specific SQL code portion needed to set a constraint.
     *
2370
     * @throws InvalidArgumentException
2371
     */
2372
    public function getUniqueConstraintDeclarationSQL($name, Index $index)
2373
    {
2374
        $columns = $index->getColumns();
2375
        $name    = new Identifier($name);
2376 2377

        if (count($columns) === 0) {
2378
            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
2379
        }
2380

2381
        return 'CONSTRAINT ' . $name->getQuotedName($this) . ' UNIQUE ('
2382
            . $this->getIndexFieldDeclarationListSQL($index)
2383
            . ')' . $this->getPartialIndexSQL($index);
2384
    }
2385 2386

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2387
     * Obtains DBMS specific SQL code portion needed to set an index
2388 2389
     * declaration to be used in statements like CREATE TABLE.
     *
2390 2391
     * @param string $name  The name of the index.
     * @param Index  $index The index definition.
Benjamin Morel's avatar
Benjamin Morel committed
2392 2393
     *
     * @return string DBMS specific SQL code portion needed to set an index.
2394
     *
2395
     * @throws InvalidArgumentException
2396
     */
2397
    public function getIndexDeclarationSQL($name, Index $index)
2398
    {
2399
        $columns = $index->getColumns();
2400
        $name    = new Identifier($name);
2401 2402

        if (count($columns) === 0) {
2403
            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
2404 2405
        }

2406
        return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name->getQuotedName($this) . ' ('
2407
            . $this->getIndexFieldDeclarationListSQL($index)
2408
            . ')' . $this->getPartialIndexSQL($index);
2409 2410
    }

2411
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2412
     * Obtains SQL code portion needed to create a custom column,
2413 2414 2415
     * e.g. when a field has the "columnDefinition" keyword.
     * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate.
     *
2416
     * @param mixed[] $columnDef
2417
     *
2418 2419
     * @return string
     */
2420
    public function getCustomTypeDeclarationSQL(array $columnDef)
2421 2422 2423 2424
    {
        return $columnDef['columnDefinition'];
    }

2425
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2426
     * Obtains DBMS specific SQL code portion needed to set an index
2427 2428
     * declaration to be used in statements like CREATE TABLE.
     *
2429
     * @param mixed[]|Index $columnsOrIndex array declaration is deprecated, prefer passing Index to this method
2430
     */
2431
    public function getIndexFieldDeclarationListSQL($columnsOrIndex) : string
2432
    {
2433 2434 2435 2436 2437 2438 2439 2440
        if ($columnsOrIndex instanceof Index) {
            return implode(', ', $columnsOrIndex->getQuotedColumns($this));
        }

        if (! is_array($columnsOrIndex)) {
            throw new InvalidArgumentException('Fields argument should be an Index or array.');
        }

2441
        $ret = [];
2442

2443
        foreach ($columnsOrIndex as $column => $definition) {
2444
            if (is_array($definition)) {
2445
                $ret[] = $column;
2446
            } else {
2447
                $ret[] = $definition;
2448 2449
            }
        }
2450

2451 2452 2453 2454
        return implode(', ', $ret);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2455
     * Returns the required SQL string that fits between CREATE ... TABLE
2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467
     * to create the table as a temporary table.
     *
     * Should be overridden in driver classes to return the correct string for the
     * specific database type.
     *
     * The default is to return the string "TEMPORARY" - this will result in a
     * SQL error for any database that does not support temporary tables, or that
     * requires a different SQL command from "CREATE TEMPORARY TABLE".
     *
     * @return string The string required to be placed between "CREATE" and "TABLE"
     *                to generate a temporary table, if possible.
     */
2468
    public function getTemporaryTableSQL()
2469 2470 2471
    {
        return 'TEMPORARY';
    }
2472

2473 2474 2475
    /**
     * Some vendors require temporary table names to be qualified specially.
     *
Benjamin Morel's avatar
Benjamin Morel committed
2476
     * @param string $tableName
2477
     *
2478 2479 2480 2481 2482 2483 2484
     * @return string
     */
    public function getTemporaryTableName($tableName)
    {
        return $tableName;
    }

2485 2486 2487 2488
    /**
     * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
     * of a field declaration to be used in statements like CREATE TABLE.
     *
Benjamin Morel's avatar
Benjamin Morel committed
2489 2490
     * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
     *                of a field declaration.
2491
     */
2492
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
2493
    {
2494 2495
        $sql  = $this->getForeignKeyBaseDeclarationSQL($foreignKey);
        $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey);
2496 2497 2498 2499 2500

        return $sql;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2501
     * Returns the FOREIGN KEY query section dealing with non-standard options
2502 2503
     * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
     *
2504
     * @param ForeignKeyConstraint $foreignKey The foreign key definition.
2505
     *
2506 2507
     * @return string
     */
2508
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
2509 2510
    {
        $query = '';
2511
        if ($this->supportsForeignKeyOnUpdate() && $foreignKey->hasOption('onUpdate')) {
2512
            $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate'));
2513
        }
2514
        if ($foreignKey->hasOption('onDelete')) {
2515
            $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
2516
        }
Benjamin Morel's avatar
Benjamin Morel committed
2517

2518 2519 2520 2521
        return $query;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2522
     * Returns the given referential action in uppercase if valid, otherwise throws an exception.
2523
     *
Benjamin Morel's avatar
Benjamin Morel committed
2524
     * @param string $action The foreign key referential action.
2525
     *
2526
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
2527
     *
2528
     * @throws InvalidArgumentException If unknown referential action given.
2529
     */
2530
    public function getForeignKeyReferentialActionSQL($action)
2531 2532 2533 2534 2535 2536 2537 2538 2539 2540
    {
        $upper = strtoupper($action);
        switch ($upper) {
            case 'CASCADE':
            case 'SET NULL':
            case 'NO ACTION':
            case 'RESTRICT':
            case 'SET DEFAULT':
                return $upper;
            default:
2541
                throw new InvalidArgumentException('Invalid foreign key action: ' . $upper);
2542 2543 2544 2545
        }
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2546
     * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
2547 2548 2549
     * of a field declaration to be used in statements like CREATE TABLE.
     *
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
2550
     *
2551
     * @throws InvalidArgumentException
2552
     */
2553
    public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey)
2554 2555
    {
        $sql = '';
2556
        if (strlen($foreignKey->getName())) {
2557
            $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' ';
2558 2559 2560
        }
        $sql .= 'FOREIGN KEY (';

2561
        if (count($foreignKey->getLocalColumns()) === 0) {
2562
            throw new InvalidArgumentException("Incomplete definition. 'local' required.");
2563
        }
2564
        if (count($foreignKey->getForeignColumns()) === 0) {
2565
            throw new InvalidArgumentException("Incomplete definition. 'foreign' required.");
2566
        }
2567
        if (strlen($foreignKey->getForeignTableName()) === 0) {
2568
            throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required.");
2569 2570
        }

2571
        return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this))
2572 2573 2574
            . ') REFERENCES '
            . $foreignKey->getQuotedForeignTableName($this) . ' ('
            . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')';
2575 2576 2577
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2578
     * Obtains DBMS specific SQL code portion needed to set the UNIQUE constraint
2579 2580
     * of a field declaration to be used in statements like CREATE TABLE.
     *
Benjamin Morel's avatar
Benjamin Morel committed
2581 2582
     * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint
     *                of a field declaration.
2583
     */
2584
    public function getUniqueFieldDeclarationSQL()
2585 2586 2587 2588 2589
    {
        return 'UNIQUE';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2590
     * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET
2591 2592
     * of a field declaration to be used in statements like CREATE TABLE.
     *
Benjamin Morel's avatar
Benjamin Morel committed
2593
     * @param string $charset The name of the charset.
2594
     *
Benjamin Morel's avatar
Benjamin Morel committed
2595 2596
     * @return string DBMS specific SQL code portion needed to set the CHARACTER SET
     *                of a field declaration.
2597
     */
2598
    public function getColumnCharsetDeclarationSQL($charset)
2599 2600 2601 2602 2603
    {
        return '';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2604
     * Obtains DBMS specific SQL code portion needed to set the COLLATION
2605 2606
     * of a field declaration to be used in statements like CREATE TABLE.
     *
Benjamin Morel's avatar
Benjamin Morel committed
2607
     * @param string $collation The name of the collation.
2608
     *
Benjamin Morel's avatar
Benjamin Morel committed
2609 2610
     * @return string DBMS specific SQL code portion needed to set the COLLATION
     *                of a field declaration.
2611
     */
2612
    public function getColumnCollationDeclarationSQL($collation)
2613
    {
2614
        return $this->supportsColumnCollation() ? 'COLLATE ' . $collation : '';
2615
    }
2616

2617 2618 2619 2620
    /**
     * Whether the platform prefers sequences for ID generation.
     * Subclasses should override this method to return TRUE if they prefer sequences.
     *
2621
     * @return bool
2622 2623 2624 2625 2626
     */
    public function prefersSequences()
    {
        return false;
    }
2627

2628 2629 2630 2631
    /**
     * Whether the platform prefers identity columns (eg. autoincrement) for ID generation.
     * Subclasses should override this method to return TRUE if they prefer identity columns.
     *
2632
     * @return bool
2633 2634 2635 2636 2637
     */
    public function prefersIdentityColumns()
    {
        return false;
    }
2638

2639 2640
    /**
     * Some platforms need the boolean values to be converted.
2641
     *
romanb's avatar
romanb committed
2642
     * The default conversion in this implementation converts to integers (false => 0, true => 1).
2643
     *
2644 2645
     * Note: if the input is not a boolean the original input might be returned.
     *
2646 2647
     * There are two contexts when converting booleans: Literals and Prepared Statements.
     * This method should handle the literal case
2648
     *
2649
     * @param mixed $item A boolean or an array of them.
2650
     *
2651
     * @return mixed A boolean database value or an array of them.
2652
     */
2653
    public function convertBooleans($item)
2654 2655 2656
    {
        if (is_array($item)) {
            foreach ($item as $k => $value) {
2657 2658
                if (! is_bool($value)) {
                    continue;
2659
                }
2660 2661

                $item[$k] = (int) $value;
2662
            }
Steve Müller's avatar
Steve Müller committed
2663
        } elseif (is_bool($item)) {
romanb's avatar
romanb committed
2664
            $item = (int) $item;
2665
        }
2666

2667 2668
        return $item;
    }
2669

2670
    /**
2671
     * Some platforms have boolean literals that needs to be correctly converted
2672 2673 2674 2675 2676
     *
     * The default conversion tries to convert value into bool "(bool)$item"
     *
     * @param mixed $item
     *
2677
     * @return bool|null
2678 2679 2680
     */
    public function convertFromBoolean($item)
    {
2681
        return $item === null ? null: (bool) $item;
2682
    }
2683

2684 2685 2686 2687
    /**
     * This method should handle the prepared statements case. When there is no
     * distinction, it's OK to use the same method.
     *
2688 2689 2690
     * Note: if the input is not a boolean the original input might be returned.
     *
     * @param mixed $item A boolean or an array of them.
2691
     *
2692
     * @return mixed A boolean database value or an array of them.
2693
     */
2694
    public function convertBooleansToDatabaseValue($item)
2695
    {
2696
        return $this->convertBooleans($item);
2697 2698
    }

2699
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2700
     * Returns the SQL specific for the platform to get the current date.
2701 2702 2703
     *
     * @return string
     */
2704
    public function getCurrentDateSQL()
2705 2706 2707 2708 2709
    {
        return 'CURRENT_DATE';
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2710
     * Returns the SQL specific for the platform to get the current time.
2711 2712 2713
     *
     * @return string
     */
2714
    public function getCurrentTimeSQL()
2715 2716 2717 2718
    {
        return 'CURRENT_TIME';
    }

2719
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2720
     * Returns the SQL specific for the platform to get the current timestamp
2721 2722 2723
     *
     * @return string
     */
2724
    public function getCurrentTimestampSQL()
2725 2726 2727
    {
        return 'CURRENT_TIMESTAMP';
    }
2728

romanb's avatar
romanb committed
2729
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2730
     * Returns the SQL for a given transaction isolation level Connection constant.
romanb's avatar
romanb committed
2731
     *
2732
     * @param int $level
2733
     *
Christophe Coevoet's avatar
Christophe Coevoet committed
2734
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
2735
     *
2736
     * @throws InvalidArgumentException
romanb's avatar
romanb committed
2737
     */
2738
    protected function _getTransactionIsolationLevelSQL($level)
romanb's avatar
romanb committed
2739 2740
    {
        switch ($level) {
2741
            case TransactionIsolationLevel::READ_UNCOMMITTED:
romanb's avatar
romanb committed
2742
                return 'READ UNCOMMITTED';
2743
            case TransactionIsolationLevel::READ_COMMITTED:
romanb's avatar
romanb committed
2744
                return 'READ COMMITTED';
2745
            case TransactionIsolationLevel::REPEATABLE_READ:
romanb's avatar
romanb committed
2746
                return 'REPEATABLE READ';
2747
            case TransactionIsolationLevel::SERIALIZABLE:
romanb's avatar
romanb committed
2748 2749
                return 'SERIALIZABLE';
            default:
2750
                throw new InvalidArgumentException('Invalid isolation level:' . $level);
2751 2752 2753
        }
    }

Benjamin Morel's avatar
Benjamin Morel committed
2754 2755 2756
    /**
     * @return string
     *
2757
     * @throws DBALException If not supported on this platform.
Benjamin Morel's avatar
Benjamin Morel committed
2758
     */
2759
    public function getListDatabasesSQL()
2760
    {
2761
        throw DBALException::notSupported(__METHOD__);
2762 2763
    }

2764 2765 2766 2767 2768
    /**
     * Returns the SQL statement for retrieving the namespaces defined in the database.
     *
     * @return string
     *
2769
     * @throws DBALException If not supported on this platform.
2770 2771 2772 2773 2774 2775
     */
    public function getListNamespacesSQL()
    {
        throw DBALException::notSupported(__METHOD__);
    }

Benjamin Morel's avatar
Benjamin Morel committed
2776 2777 2778 2779 2780
    /**
     * @param string $database
     *
     * @return string
     *
2781
     * @throws DBALException If not supported on this platform.
Benjamin Morel's avatar
Benjamin Morel committed
2782
     */
2783
    public function getListSequencesSQL($database)
2784
    {
2785
        throw DBALException::notSupported(__METHOD__);
2786 2787
    }

Benjamin Morel's avatar
Benjamin Morel committed
2788 2789 2790 2791 2792
    /**
     * @param string $table
     *
     * @return string
     *
2793
     * @throws DBALException If not supported on this platform.
Benjamin Morel's avatar
Benjamin Morel committed
2794
     */
2795
    public function getListTableConstraintsSQL($table)
2796
    {
2797
        throw DBALException::notSupported(__METHOD__);
2798 2799
    }

Benjamin Morel's avatar
Benjamin Morel committed
2800 2801 2802 2803 2804 2805
    /**
     * @param string      $table
     * @param string|null $database
     *
     * @return string
     *
2806
     * @throws DBALException If not supported on this platform.
Benjamin Morel's avatar
Benjamin Morel committed
2807
     */
2808
    public function getListTableColumnsSQL($table, $database = null)
2809
    {
2810
        throw DBALException::notSupported(__METHOD__);
2811 2812
    }

Benjamin Morel's avatar
Benjamin Morel committed
2813 2814 2815
    /**
     * @return string
     *
2816
     * @throws DBALException If not supported on this platform.
Benjamin Morel's avatar
Benjamin Morel committed
2817
     */
2818
    public function getListTablesSQL()
2819
    {
2820
        throw DBALException::notSupported(__METHOD__);
2821 2822
    }

Benjamin Morel's avatar
Benjamin Morel committed
2823 2824 2825
    /**
     * @return string
     *
2826
     * @throws DBALException If not supported on this platform.
Benjamin Morel's avatar
Benjamin Morel committed
2827
     */
2828
    public function getListUsersSQL()
2829
    {
2830
        throw DBALException::notSupported(__METHOD__);
2831 2832
    }

2833
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2834
     * Returns the SQL to list all views of a database or user.
2835 2836
     *
     * @param string $database
2837
     *
2838
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
2839
     *
2840
     * @throws DBALException If not supported on this platform.
2841
     */
2842
    public function getListViewsSQL($database)
2843
    {
2844
        throw DBALException::notSupported(__METHOD__);
2845 2846
    }

2847
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2848
     * Returns the list of indexes for the current database.
2849
     *
2850 2851
     * The current database parameter is optional but will always be passed
     * when using the SchemaManager API and is the database the given table is in.
2852
     *
2853 2854 2855
     * Attention: Some platforms only support currentDatabase when they
     * are connected with that database. Cross-database information schema
     * requests may be impossible.
2856
     *
2857
     * @param string $table
2858
     * @param string $currentDatabase
2859
     *
Christophe Coevoet's avatar
Christophe Coevoet committed
2860
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
2861
     *
2862
     * @throws DBALException If not supported on this platform.
2863 2864
     */
    public function getListTableIndexesSQL($table, $currentDatabase = null)
2865
    {
2866
        throw DBALException::notSupported(__METHOD__);
2867 2868
    }

Benjamin Morel's avatar
Benjamin Morel committed
2869 2870 2871 2872 2873
    /**
     * @param string $table
     *
     * @return string
     *
2874
     * @throws DBALException If not supported on this platform.
Benjamin Morel's avatar
Benjamin Morel committed
2875
     */
2876
    public function getListTableForeignKeysSQL($table)
2877
    {
2878
        throw DBALException::notSupported(__METHOD__);
2879 2880
    }

Benjamin Morel's avatar
Benjamin Morel committed
2881 2882 2883 2884 2885 2886
    /**
     * @param string $name
     * @param string $sql
     *
     * @return string
     *
2887
     * @throws DBALException If not supported on this platform.
Benjamin Morel's avatar
Benjamin Morel committed
2888
     */
2889
    public function getCreateViewSQL($name, $sql)
2890
    {
2891
        throw DBALException::notSupported(__METHOD__);
2892 2893
    }

Benjamin Morel's avatar
Benjamin Morel committed
2894 2895 2896 2897 2898
    /**
     * @param string $name
     *
     * @return string
     *
2899
     * @throws DBALException If not supported on this platform.
Benjamin Morel's avatar
Benjamin Morel committed
2900
     */
2901
    public function getDropViewSQL($name)
2902
    {
2903
        throw DBALException::notSupported(__METHOD__);
2904 2905
    }

2906
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2907
     * Returns the SQL snippet to drop an existing sequence.
2908
     *
jeroendedauw's avatar
jeroendedauw committed
2909
     * @param Sequence|string $sequence
2910 2911
     *
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
2912
     *
2913
     * @throws DBALException If not supported on this platform.
2914
     */
2915
    public function getDropSequenceSQL($sequence)
2916
    {
2917
        throw DBALException::notSupported(__METHOD__);
2918 2919
    }

Benjamin Morel's avatar
Benjamin Morel committed
2920 2921 2922 2923 2924
    /**
     * @param string $sequenceName
     *
     * @return string
     *
2925
     * @throws DBALException If not supported on this platform.
Benjamin Morel's avatar
Benjamin Morel committed
2926
     */
2927
    public function getSequenceNextValSQL($sequenceName)
2928
    {
2929
        throw DBALException::notSupported(__METHOD__);
romanb's avatar
romanb committed
2930
    }
2931

2932
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2933
     * Returns the SQL to create a new database.
2934
     *
Benjamin Morel's avatar
Benjamin Morel committed
2935
     * @param string $database The name of the database that should be created.
2936 2937
     *
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
2938
     *
2939
     * @throws DBALException If not supported on this platform.
2940
     */
2941
    public function getCreateDatabaseSQL($database)
2942
    {
2943
        throw DBALException::notSupported(__METHOD__);
2944 2945
    }

romanb's avatar
romanb committed
2946
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2947
     * Returns the SQL to set the transaction isolation level.
romanb's avatar
romanb committed
2948
     *
2949
     * @param int $level
2950
     *
Christophe Coevoet's avatar
Christophe Coevoet committed
2951
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
2952
     *
2953
     * @throws DBALException If not supported on this platform.
romanb's avatar
romanb committed
2954
     */
2955
    public function getSetTransactionIsolationSQL($level)
romanb's avatar
romanb committed
2956
    {
2957
        throw DBALException::notSupported(__METHOD__);
romanb's avatar
romanb committed
2958
    }
2959

2960
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2961 2962
     * Obtains DBMS specific SQL to be used to create datetime fields in
     * statements like CREATE TABLE.
2963
     *
2964
     * @param mixed[] $fieldDeclaration
2965
     *
2966
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
2967
     *
2968
     * @throws DBALException If not supported on this platform.
2969
     */
2970
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
2971
    {
2972
        throw DBALException::notSupported(__METHOD__);
2973
    }
2974 2975

    /**
Benjamin Morel's avatar
Benjamin Morel committed
2976
     * Obtains DBMS specific SQL to be used to create datetime with timezone offset fields.
2977
     *
2978
     * @param mixed[] $fieldDeclaration
2979
     *
Christophe Coevoet's avatar
Christophe Coevoet committed
2980
     * @return string
2981 2982 2983
     */
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
    {
2984
        return $this->getDateTimeTypeDeclarationSQL($fieldDeclaration);
2985
    }
2986

2987
    /**
Benjamin Morel's avatar
Benjamin Morel committed
2988
     * Obtains DBMS specific SQL to be used to create date fields in statements
2989
     * like CREATE TABLE.
2990
     *
2991
     * @param mixed[] $fieldDeclaration
2992
     *
2993
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
2994
     *
2995
     * @throws DBALException If not supported on this platform.
2996
     */
2997
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
2998
    {
2999
        throw DBALException::notSupported(__METHOD__);
3000
    }
3001

3002
    /**
Benjamin Morel's avatar
Benjamin Morel committed
3003
     * Obtains DBMS specific SQL to be used to create time fields in statements
3004 3005
     * like CREATE TABLE.
     *
3006
     * @param mixed[] $fieldDeclaration
3007
     *
3008
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
3009
     *
3010
     * @throws DBALException If not supported on this platform.
3011
     */
3012
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
3013
    {
3014
        throw DBALException::notSupported(__METHOD__);
3015 3016
    }

Benjamin Morel's avatar
Benjamin Morel committed
3017
    /**
3018
     * @param mixed[] $fieldDeclaration
Benjamin Morel's avatar
Benjamin Morel committed
3019 3020 3021
     *
     * @return string
     */
3022 3023 3024 3025 3026
    public function getFloatDeclarationSQL(array $fieldDeclaration)
    {
        return 'DOUBLE PRECISION';
    }

romanb's avatar
romanb committed
3027 3028 3029
    /**
     * Gets the default transaction isolation level of the platform.
     *
3030
     * @see TransactionIsolationLevel
3031 3032
     *
     * @return int The default isolation level.
romanb's avatar
romanb committed
3033 3034 3035
     */
    public function getDefaultTransactionIsolationLevel()
    {
3036
        return TransactionIsolationLevel::READ_COMMITTED;
romanb's avatar
romanb committed
3037
    }
3038

3039
    /* supports*() methods */
3040 3041 3042 3043

    /**
     * Whether the platform supports sequences.
     *
3044
     * @return bool
3045
     */
3046 3047 3048 3049
    public function supportsSequences()
    {
        return false;
    }
3050 3051 3052

    /**
     * Whether the platform supports identity columns.
Benjamin Morel's avatar
Benjamin Morel committed
3053
     *
Pascal Borreli's avatar
Pascal Borreli committed
3054
     * Identity columns are columns that receive an auto-generated value from the
3055 3056
     * database on insert of a row.
     *
3057
     * @return bool
3058
     */
3059 3060 3061 3062
    public function supportsIdentityColumns()
    {
        return false;
    }
3063

3064 3065 3066 3067 3068 3069 3070
    /**
     * Whether the platform emulates identity columns through sequences.
     *
     * Some platforms that do not support identity columns natively
     * but support sequences can emulate identity columns by using
     * sequences.
     *
3071
     * @return bool
3072 3073 3074 3075 3076 3077 3078 3079 3080
     */
    public function usesSequenceEmulatedIdentityColumns()
    {
        return false;
    }

    /**
     * Returns the name of the sequence for a particular identity column in a particular table.
     *
3081 3082
     * @see    usesSequenceEmulatedIdentityColumns
     *
3083 3084 3085 3086 3087
     * @param string $tableName  The name of the table to return the sequence name for.
     * @param string $columnName The name of the identity column in the table to return the sequence name for.
     *
     * @return string
     *
3088
     * @throws DBALException If not supported on this platform.
3089 3090 3091 3092 3093 3094
     */
    public function getIdentitySequenceName($tableName, $columnName)
    {
        throw DBALException::notSupported(__METHOD__);
    }

3095 3096 3097
    /**
     * Whether the platform supports indexes.
     *
3098
     * @return bool
3099
     */
3100 3101 3102 3103
    public function supportsIndexes()
    {
        return true;
    }
3104

3105 3106 3107
    /**
     * Whether the platform supports partial indexes.
     *
3108
     * @return bool
3109 3110 3111 3112 3113 3114
     */
    public function supportsPartialIndexes()
    {
        return false;
    }

3115 3116 3117 3118 3119 3120 3121 3122
    /**
     * Whether the platform supports indexes with column length definitions.
     */
    public function supportsColumnLengthIndexes() : bool
    {
        return false;
    }

3123 3124 3125
    /**
     * Whether the platform supports altering tables.
     *
3126
     * @return bool
3127
     */
3128 3129 3130 3131 3132
    public function supportsAlterTable()
    {
        return true;
    }

3133 3134 3135
    /**
     * Whether the platform supports transactions.
     *
3136
     * @return bool
3137
     */
3138 3139 3140 3141
    public function supportsTransactions()
    {
        return true;
    }
3142 3143 3144 3145

    /**
     * Whether the platform supports savepoints.
     *
3146
     * @return bool
3147
     */
3148 3149 3150 3151
    public function supportsSavepoints()
    {
        return true;
    }
3152

3153 3154 3155
    /**
     * Whether the platform supports releasing savepoints.
     *
3156
     * @return bool
3157 3158 3159 3160 3161 3162
     */
    public function supportsReleaseSavepoints()
    {
        return $this->supportsSavepoints();
    }

3163 3164 3165
    /**
     * Whether the platform supports primary key constraints.
     *
3166
     * @return bool
3167
     */
3168 3169 3170 3171
    public function supportsPrimaryConstraints()
    {
        return true;
    }
3172 3173

    /**
Benjamin Morel's avatar
Benjamin Morel committed
3174
     * Whether the platform supports foreign key constraints.
3175
     *
3176
     * @return bool
3177
     */
3178 3179 3180 3181
    public function supportsForeignKeyConstraints()
    {
        return true;
    }
3182 3183

    /**
Benjamin Morel's avatar
Benjamin Morel committed
3184
     * Whether this platform supports onUpdate in foreign key constraints.
3185
     *
3186
     * @return bool
3187 3188 3189
     */
    public function supportsForeignKeyOnUpdate()
    {
Sergei Morozov's avatar
Sergei Morozov committed
3190
        return $this->supportsForeignKeyConstraints();
3191
    }
3192

3193 3194
    /**
     * Whether the platform supports database schemas.
3195
     *
3196
     * @return bool
3197 3198 3199 3200 3201
     */
    public function supportsSchemas()
    {
        return false;
    }
3202

3203
    /**
Benjamin Morel's avatar
Benjamin Morel committed
3204
     * Whether this platform can emulate schemas.
3205 3206 3207 3208 3209
     *
     * Platforms that either support or emulate schemas don't automatically
     * filter a schema for the namespaced elements in {@link
     * AbstractManager#createSchema}.
     *
3210
     * @return bool
3211 3212 3213 3214 3215 3216
     */
    public function canEmulateSchemas()
    {
        return false;
    }

3217 3218 3219 3220 3221
    /**
     * Returns the default schema name.
     *
     * @return string
     *
3222
     * @throws DBALException If not supported on this platform.
3223
     */
3224
    public function getDefaultSchemaName()
3225 3226 3227 3228
    {
        throw DBALException::notSupported(__METHOD__);
    }

3229
    /**
Benjamin Morel's avatar
Benjamin Morel committed
3230 3231
     * Whether this platform supports create database.
     *
3232 3233
     * Some databases don't allow to create and drop databases at all or only with certain tools.
     *
3234
     * @return bool
3235 3236 3237 3238 3239 3240
     */
    public function supportsCreateDropDatabase()
    {
        return true;
    }

3241
    /**
Benjamin Morel's avatar
Benjamin Morel committed
3242
     * Whether the platform supports getting the affected rows of a recent update/delete type query.
3243
     *
3244
     * @return bool
3245
     */
3246 3247 3248 3249
    public function supportsGettingAffectedRows()
    {
        return true;
    }
3250

3251
    /**
Benjamin Morel's avatar
Benjamin Morel committed
3252
     * Whether this platform support to add inline column comments as postfix.
3253
     *
3254
     * @return bool
3255 3256 3257 3258 3259 3260 3261
     */
    public function supportsInlineColumnComments()
    {
        return false;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
3262
     * Whether this platform support the proprietary syntax "COMMENT ON asset".
3263
     *
3264
     * @return bool
3265 3266 3267 3268 3269 3270
     */
    public function supportsCommentOnStatement()
    {
        return false;
    }

3271 3272 3273
    /**
     * Does this platform have native guid type.
     *
3274
     * @return bool
3275 3276 3277 3278 3279 3280
     */
    public function hasNativeGuidType()
    {
        return false;
    }

3281 3282 3283
    /**
     * Does this platform have native JSON type.
     *
3284
     * @return bool
3285 3286 3287 3288 3289 3290
     */
    public function hasNativeJsonType()
    {
        return false;
    }

3291 3292
    /**
     * @deprecated
3293
     *
3294 3295
     * @todo Remove in 3.0
     */
3296
    public function getIdentityColumnNullInsertSQL()
3297
    {
3298
        return '';
3299 3300
    }

3301
    /**
Benjamin Morel's avatar
Benjamin Morel committed
3302
     * Whether this platform supports views.
3303
     *
3304
     * @return bool
3305 3306 3307 3308 3309 3310
     */
    public function supportsViews()
    {
        return true;
    }

3311 3312 3313
    /**
     * Does this platform support column collation?
     *
3314
     * @return bool
3315 3316 3317 3318 3319 3320
     */
    public function supportsColumnCollation()
    {
        return false;
    }

3321
    /**
3322 3323
     * Gets the format string, as accepted by the date() function, that describes
     * the format of a stored datetime value of this platform.
3324
     *
3325
     * @return string The format string.
3326 3327 3328 3329 3330 3331
     */
    public function getDateTimeFormatString()
    {
        return 'Y-m-d H:i:s';
    }

3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342
    /**
     * Gets the format string, as accepted by the date() function, that describes
     * the format of a stored datetime with timezone value of this platform.
     *
     * @return string The format string.
     */
    public function getDateTimeTzFormatString()
    {
        return 'Y-m-d H:i:s';
    }

3343 3344 3345
    /**
     * Gets the format string, as accepted by the date() function, that describes
     * the format of a stored date value of this platform.
3346
     *
3347 3348
     * @return string The format string.
     */
3349 3350
    public function getDateFormatString()
    {
3351
        return 'Y-m-d';
3352
    }
3353

3354 3355 3356
    /**
     * Gets the format string, as accepted by the date() function, that describes
     * the format of a stored time value of this platform.
3357
     *
3358 3359
     * @return string The format string.
     */
3360 3361 3362 3363 3364
    public function getTimeFormatString()
    {
        return 'H:i:s';
    }

3365
    /**
Benjamin Morel's avatar
Benjamin Morel committed
3366
     * Adds an driver-specific LIMIT clause to the query.
3367
     *
3368 3369 3370
     * @param string   $query
     * @param int|null $limit
     * @param int|null $offset
3371
     *
3372
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
3373 3374
     *
     * @throws DBALException
3375 3376 3377
     */
    final public function modifyLimitQuery($query, $limit, $offset = null)
    {
3378
        if ($limit !== null) {
3379
            $limit = (int) $limit;
3380 3381
        }

3382
        $offset = (int) $offset;
3383

3384 3385
        if ($offset < 0) {
            throw new DBALException(sprintf(
3386
                'Offset must be a positive integer or zero, %d given',
3387 3388 3389 3390 3391 3392 3393 3394 3395
                $offset
            ));
        }

        if ($offset > 0 && ! $this->supportsLimitOffset()) {
            throw new DBALException(sprintf(
                'Platform %s does not support offset values in limit queries.',
                $this->getName()
            ));
3396 3397 3398 3399 3400 3401
        }

        return $this->doModifyLimitQuery($query, $limit, $offset);
    }

    /**
3402
     * Adds an platform-specific LIMIT clause to the query.
3403
     *
3404 3405 3406
     * @param string   $query
     * @param int|null $limit
     * @param int|null $offset
3407
     *
3408 3409 3410
     * @return string
     */
    protected function doModifyLimitQuery($query, $limit, $offset)
3411
    {
3412
        if ($limit !== null) {
3413
            $query .= ' LIMIT ' . $limit;
3414 3415
        }

3416
        if ($offset > 0) {
3417 3418 3419
            $query .= ' OFFSET ' . $offset;
        }

3420 3421
        return $query;
    }
3422

3423
    /**
Benjamin Morel's avatar
Benjamin Morel committed
3424
     * Whether the database platform support offsets in modify limit clauses.
3425
     *
3426
     * @return bool
3427 3428 3429 3430 3431 3432
     */
    public function supportsLimitOffset()
    {
        return true;
    }

3433 3434
    /**
     * Gets the character casing of a column in an SQL result set of this platform.
3435
     *
3436
     * @param string $column The column name for which to get the correct character casing.
3437
     *
3438 3439
     * @return string The column name in the character casing used in SQL result sets.
     */
3440
    public function getSQLResultCasing($column)
3441 3442 3443
    {
        return $column;
    }
3444

3445 3446 3447
    /**
     * Makes any fixes to a name of a schema element (table, sequence, ...) that are required
     * by restrictions of the platform, like a maximum length.
3448
     *
3449
     * @param string $schemaElementName
3450
     *
3451 3452 3453 3454 3455 3456
     * @return string
     */
    public function fixSchemaElementName($schemaElementName)
    {
        return $schemaElementName;
    }
3457

3458
    /**
Pascal Borreli's avatar
Pascal Borreli committed
3459
     * Maximum length of any given database identifier, like tables or column names.
3460
     *
3461
     * @return int
3462 3463 3464 3465 3466 3467
     */
    public function getMaxIdentifierLength()
    {
        return 63;
    }

3468
    /**
Benjamin Morel's avatar
Benjamin Morel committed
3469
     * Returns the insert SQL for an empty insert statement.
3470
     *
3471 3472
     * @param string $tableName
     * @param string $identifierColumnName
3473
     *
Benjamin Morel's avatar
Benjamin Morel committed
3474
     * @return string
3475
     */
3476
    public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName)
3477 3478 3479
    {
        return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (null)';
    }
3480 3481

    /**
Benjamin Morel's avatar
Benjamin Morel committed
3482
     * Generates a Truncate Table SQL statement for a given table.
3483 3484 3485 3486
     *
     * Cascade is not supported on many platforms but would optionally cascade the truncate by
     * following the foreign keys.
     *
3487 3488
     * @param string $tableName
     * @param bool   $cascade
3489
     *
3490 3491
     * @return string
     */
3492
    public function getTruncateTableSQL($tableName, $cascade = false)
3493
    {
3494 3495 3496
        $tableIdentifier = new Identifier($tableName);

        return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this);
3497
    }
3498 3499 3500

    /**
     * This is for test reasons, many vendors have special requirements for dummy statements.
3501
     *
3502 3503 3504 3505
     * @return string
     */
    public function getDummySelectSQL()
    {
3506 3507 3508
        $expression = func_num_args() > 0 ? func_get_arg(0) : '1';

        return sprintf('SELECT %s', $expression);
3509
    }
3510 3511

    /**
Benjamin Morel's avatar
Benjamin Morel committed
3512
     * Returns the SQL to create a new savepoint.
3513 3514
     *
     * @param string $savepoint
3515
     *
3516 3517 3518 3519 3520 3521 3522 3523
     * @return string
     */
    public function createSavePoint($savepoint)
    {
        return 'SAVEPOINT ' . $savepoint;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
3524
     * Returns the SQL to release a savepoint.
3525 3526
     *
     * @param string $savepoint
3527
     *
3528 3529 3530 3531 3532 3533 3534 3535
     * @return string
     */
    public function releaseSavePoint($savepoint)
    {
        return 'RELEASE SAVEPOINT ' . $savepoint;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
3536
     * Returns the SQL to rollback a savepoint.
3537 3538
     *
     * @param string $savepoint
3539
     *
3540 3541 3542 3543 3544 3545
     * @return string
     */
    public function rollbackSavePoint($savepoint)
    {
        return 'ROLLBACK TO SAVEPOINT ' . $savepoint;
    }
3546 3547

    /**
Benjamin Morel's avatar
Benjamin Morel committed
3548
     * Returns the keyword list instance of this platform.
3549
     *
3550
     * @return KeywordList
Benjamin Morel's avatar
Benjamin Morel committed
3551
     *
3552
     * @throws DBALException If no keyword list is specified.
3553 3554 3555
     */
    final public function getReservedKeywordsList()
    {
3556
        // Check for an existing instantiation of the keywords class.
3557 3558
        if ($this->_keywords) {
            return $this->_keywords;
3559 3560
        }

3561 3562 3563
        $class    = $this->getReservedKeywordsClass();
        $keywords = new $class();
        if (! $keywords instanceof KeywordList) {
3564 3565
            throw DBALException::notSupported(__METHOD__);
        }
3566 3567

        // Store the instance so it doesn't need to be generated on every request.
3568
        $this->_keywords = $keywords;
3569

3570 3571
        return $keywords;
    }
3572

3573
    /**
Benjamin Morel's avatar
Benjamin Morel committed
3574
     * Returns the class name of the reserved keywords list.
3575
     *
3576
     * @return string
Benjamin Morel's avatar
Benjamin Morel committed
3577
     *
3578
     * @throws DBALException If not supported on this platform.
3579 3580 3581 3582 3583
     */
    protected function getReservedKeywordsClass()
    {
        throw DBALException::notSupported(__METHOD__);
    }
3584 3585

    /**
3586 3587 3588 3589
     * Quotes a literal string.
     * This method is NOT meant to fix SQL injections!
     * It is only meant to escape this platform's string literal
     * quote character inside the given literal string.
3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610
     *
     * @param string $str The literal string to be quoted.
     *
     * @return string The quoted literal string.
     */
    public function quoteStringLiteral($str)
    {
        $c = $this->getStringLiteralQuoteCharacter();

        return $c . str_replace($c, $c . $c, $str) . $c;
    }

    /**
     * Gets the character used for string literal quoting.
     *
     * @return string
     */
    public function getStringLiteralQuoteCharacter()
    {
        return "'";
    }
3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621

    /**
     * Escapes metacharacters in a string intended to be used with a LIKE
     * operator.
     *
     * @param string $inputString a literal, unquoted string
     * @param string $escapeChar  should be reused by the caller in the LIKE
     *                            expression.
     */
    final public function escapeStringForLike(string $inputString, string $escapeChar) : string
    {
3622
        return preg_replace(
3623
            '~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u',
3624 3625 3626
            addcslashes($escapeChar, '\\') . '$1',
            $inputString
        );
3627 3628
    }

3629
    protected function getLikeWildcardCharacters() : string
3630
    {
3631
        return '%_';
3632
    }
3633
}