Completed
Pull Request — 2.10.x (#4009)
by Grégoire
08:50
created

AbstractPlatform::getCreateConstraintSQL()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6.042

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 17
cts 19
cp 0.8947
rs 8.7857
c 0
b 0
f 0
cc 6
nc 10
nop 2
crap 6.042
1
<?php
2
3
namespace Doctrine\DBAL\Platforms;
4
5
use Doctrine\Common\EventManager;
6
use Doctrine\DBAL\DBALException;
7
use Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs;
8
use Doctrine\DBAL\Event\SchemaAlterTableChangeColumnEventArgs;
9
use Doctrine\DBAL\Event\SchemaAlterTableEventArgs;
10
use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs;
11
use Doctrine\DBAL\Event\SchemaAlterTableRenameColumnEventArgs;
12
use Doctrine\DBAL\Event\SchemaCreateTableColumnEventArgs;
13
use Doctrine\DBAL\Event\SchemaCreateTableEventArgs;
14
use Doctrine\DBAL\Event\SchemaDropTableEventArgs;
15
use Doctrine\DBAL\Events;
16
use Doctrine\DBAL\Platforms\Keywords\KeywordList;
17
use Doctrine\DBAL\Schema\Column;
18
use Doctrine\DBAL\Schema\ColumnDiff;
19
use Doctrine\DBAL\Schema\Constraint;
20
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
21
use Doctrine\DBAL\Schema\Identifier;
22
use Doctrine\DBAL\Schema\Index;
23
use Doctrine\DBAL\Schema\Sequence;
24
use Doctrine\DBAL\Schema\Table;
25
use Doctrine\DBAL\Schema\TableDiff;
26
use Doctrine\DBAL\TransactionIsolationLevel;
27
use Doctrine\DBAL\Types;
28
use Doctrine\DBAL\Types\Type;
29
use InvalidArgumentException;
30
use UnexpectedValueException;
31
use function addcslashes;
32
use function array_map;
33
use function array_merge;
34
use function array_unique;
35
use function array_values;
36
use function assert;
37
use function count;
38
use function explode;
39
use function func_get_arg;
40
use function func_get_args;
41
use function func_num_args;
42
use function implode;
43
use function in_array;
44
use function is_array;
45
use function is_bool;
46
use function is_int;
47
use function is_string;
48
use function preg_quote;
49
use function preg_replace;
50
use function sprintf;
51
use function str_replace;
52
use function strlen;
53
use function strpos;
54
use function strtolower;
55
use function strtoupper;
56
use function trigger_error;
57
use const E_USER_DEPRECATED;
58
59
/**
60
 * Base class for all DatabasePlatforms. The DatabasePlatforms are the central
61
 * point of abstraction of platform-specific behaviors, features and SQL dialects.
62
 * They are a passive source of information.
63
 *
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
    protected $doctrineTypeMapping = null;
134
135
    /**
136
     * Contains a list of all columns that should generate parseable column comments for type-detection
137
     * in reverse engineering scenarios.
138
     *
139
     * @var string[]|null
140
     */
141
    protected $doctrineTypeComments = null;
142
143
    /** @var EventManager */
144
    protected $_eventManager;
145
146
    /**
147
     * Holds the KeywordList instance for the current platform.
148
     *
149
     * @var KeywordList|null
150
     */
151
    protected $_keywords;
152
153 63754
    public function __construct()
154
    {
155 63754
    }
156
157
    /**
158
     * Sets the EventManager used by the Platform.
159
     *
160
     * @return void
161
     */
162 60319
    public function setEventManager(EventManager $eventManager)
163
    {
164 60319
        $this->_eventManager = $eventManager;
165 60319
    }
166
167
    /**
168
     * Gets the EventManager used by the Platform.
169
     *
170
     * @return EventManager
171
     */
172 57777
    public function getEventManager()
173
    {
174 57777
        return $this->_eventManager;
175
    }
176
177
    /**
178
     * Returns the SQL snippet that declares a boolean column.
179
     *
180
     * @param mixed[] $columnDef
181
     *
182
     * @return string
183
     */
184
    abstract public function getBooleanTypeDeclarationSQL(array $columnDef);
185
186
    /**
187
     * Returns the SQL snippet that declares a 4 byte integer column.
188
     *
189
     * @param mixed[] $columnDef
190
     *
191
     * @return string
192
     */
193
    abstract public function getIntegerTypeDeclarationSQL(array $columnDef);
194
195
    /**
196
     * Returns the SQL snippet that declares an 8 byte integer column.
197
     *
198
     * @param mixed[] $columnDef
199
     *
200
     * @return string
201
     */
202
    abstract public function getBigIntTypeDeclarationSQL(array $columnDef);
203
204
    /**
205
     * Returns the SQL snippet that declares a 2 byte integer column.
206
     *
207
     * @param mixed[] $columnDef
208
     *
209
     * @return string
210
     */
211
    abstract public function getSmallIntTypeDeclarationSQL(array $columnDef);
212
213
    /**
214
     * Returns the SQL snippet that declares common properties of an integer column.
215
     *
216
     * @param mixed[] $columnDef
217
     *
218
     * @return string
219
     */
220
    abstract protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef);
221
222
    /**
223
     * Lazy load Doctrine Type Mappings.
224
     *
225
     * @return void
226
     */
227
    abstract protected function initializeDoctrineTypeMappings();
228
229
    /**
230
     * Initializes Doctrine Type Mappings with the platform defaults
231
     * and with all additional type mappings.
232
     *
233
     * @return void
234
     */
235 57815
    private function initializeAllDoctrineTypeMappings()
236
    {
237 57815
        $this->initializeDoctrineTypeMappings();
238
239 57815
        foreach (Type::getTypesMap() as $typeName => $className) {
240 57815
            foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) {
241 29898
                $this->doctrineTypeMapping[$dbType] = $typeName;
242
            }
243
        }
244 57815
    }
245
246
    /**
247
     * Returns the SQL snippet used to declare a VARCHAR column type.
248
     *
249
     * @param mixed[] $field
250
     *
251
     * @return string
252
     */
253 59478
    public function getVarcharTypeDeclarationSQL(array $field)
254
    {
255 59478
        if (! isset($field['length'])) {
256 56298
            $field['length'] = $this->getVarcharDefaultLength();
257
        }
258
259 59478
        $fixed = $field['fixed'] ?? false;
260
261 59478
        $maxLength = $fixed
262 57927
            ? $this->getCharMaxLength()
263 59478
            : $this->getVarcharMaxLength();
264
265 59478
        if ($field['length'] > $maxLength) {
266
            return $this->getClobTypeDeclarationSQL($field);
267
        }
268
269 59478
        return $this->getVarcharTypeDeclarationSQLSnippet($field['length'], $fixed);
270
    }
271
272
    /**
273
     * Returns the SQL snippet used to declare a BINARY/VARBINARY column type.
274
     *
275
     * @param mixed[] $field The column definition.
276
     *
277
     * @return string
278
     */
279 56538
    public function getBinaryTypeDeclarationSQL(array $field)
280
    {
281 56538
        if (! isset($field['length'])) {
282 56495
            $field['length'] = $this->getBinaryDefaultLength();
283
        }
284
285 56538
        $fixed = $field['fixed'] ?? false;
286
287 56538
        $maxLength = $this->getBinaryMaxLength();
288
289 56538
        if ($field['length'] > $maxLength) {
290 54362
            if ($maxLength > 0) {
291 54108
                @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
292 12
                    'Binary field length %d is greater than supported by the platform (%d). Reduce the field length or use a BLOB field instead.',
293 54108
                    $field['length'],
294 54108
                    $maxLength
295 54108
                ), E_USER_DEPRECATED);
296
            }
297
298 54362
            return $this->getBlobTypeDeclarationSQL($field);
299
        }
300
301 56526
        return $this->getBinaryTypeDeclarationSQLSnippet($field['length'], $fixed);
302
    }
303
304
    /**
305
     * Returns the SQL snippet to declare a GUID/UUID field.
306
     *
307
     * By default this maps directly to a CHAR(36) and only maps to more
308
     * special datatypes when the underlying databases support this datatype.
309
     *
310
     * @param mixed[] $field
311
     *
312
     * @return string
313
     */
314 55625
    public function getGuidTypeDeclarationSQL(array $field)
315
    {
316 55625
        $field['length'] = 36;
317 55625
        $field['fixed']  = true;
318
319 55625
        return $this->getVarcharTypeDeclarationSQL($field);
320
    }
321
322
    /**
323
     * Returns the SQL snippet to declare a JSON field.
324
     *
325
     * By default this maps directly to a CLOB and only maps to more
326
     * special datatypes when the underlying databases support this datatype.
327
     *
328
     * @param mixed[] $field
329
     *
330
     * @return string
331
     */
332 53975
    public function getJsonTypeDeclarationSQL(array $field)
333
    {
334 53975
        return $this->getClobTypeDeclarationSQL($field);
335
    }
336
337
    /**
338
     * @param int  $length
339
     * @param bool $fixed
340
     *
341
     * @return string
342
     *
343
     * @throws DBALException If not supported on this platform.
344
     */
345
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
346
    {
347
        throw DBALException::notSupported('VARCHARs not supported by Platform.');
348
    }
349
350
    /**
351
     * Returns the SQL snippet used to declare a BINARY/VARBINARY column type.
352
     *
353
     * @param int  $length The length of the column.
354
     * @param bool $fixed  Whether the column length is fixed.
355
     *
356
     * @return string
357
     *
358
     * @throws DBALException If not supported on this platform.
359
     */
360
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
361
    {
362
        throw DBALException::notSupported('BINARY/VARBINARY column types are not supported by this platform.');
363
    }
364
365
    /**
366
     * Returns the SQL snippet used to declare a CLOB column type.
367
     *
368
     * @param mixed[] $field
369
     *
370
     * @return string
371
     */
372
    abstract public function getClobTypeDeclarationSQL(array $field);
373
374
    /**
375
     * Returns the SQL Snippet used to declare a BLOB column type.
376
     *
377
     * @param mixed[] $field
378
     *
379
     * @return string
380
     */
381
    abstract public function getBlobTypeDeclarationSQL(array $field);
382
383
    /**
384
     * Gets the name of the platform.
385
     *
386
     * @return string
387
     */
388
    abstract public function getName();
389
390
    /**
391
     * Registers a doctrine type to be used in conjunction with a column type of this platform.
392
     *
393
     * @param string $dbType
394
     * @param string $doctrineType
395
     *
396
     * @return void
397
     *
398
     * @throws DBALException If the type is not found.
399
     */
400 55931
    public function registerDoctrineTypeMapping($dbType, $doctrineType)
401
    {
402 55931
        if ($this->doctrineTypeMapping === null) {
403 55236
            $this->initializeAllDoctrineTypeMappings();
404
        }
405
406 55931
        if (! Types\Type::hasType($doctrineType)) {
407 53516
            throw DBALException::typeNotFound($doctrineType);
408
        }
409
410 55913
        $dbType                             = strtolower($dbType);
411 55913
        $this->doctrineTypeMapping[$dbType] = $doctrineType;
412
413 55913
        $doctrineType = Type::getType($doctrineType);
414
415 55913
        if (! $doctrineType->requiresSQLCommentHint($this)) {
416 55895
            return;
417
        }
418
419 53493
        $this->markDoctrineTypeCommented($doctrineType);
420 53493
    }
421
422
    /**
423
     * Gets the Doctrine type that is mapped for the given database column type.
424
     *
425
     * @param string $dbType
426
     *
427
     * @return string
428
     *
429
     * @throws DBALException
430
     */
431 57833
    public function getDoctrineTypeMapping($dbType)
432
    {
433 57833
        if ($this->doctrineTypeMapping === null) {
434 57737
            $this->initializeAllDoctrineTypeMappings();
435
        }
436
437 57833
        $dbType = strtolower($dbType);
438
439 57833
        if (! isset($this->doctrineTypeMapping[$dbType])) {
440 53562
            throw new DBALException('Unknown database type ' . $dbType . ' requested, ' . static::class . ' may not support it.');
441
        }
442
443 57815
        return $this->doctrineTypeMapping[$dbType];
444
    }
445
446
    /**
447
     * Checks if a database type is currently supported by this platform.
448
     *
449
     * @param string $dbType
450
     *
451
     * @return bool
452
     */
453 55277
    public function hasDoctrineTypeMappingFor($dbType)
454
    {
455 55277
        if ($this->doctrineTypeMapping === null) {
456 52049
            $this->initializeAllDoctrineTypeMappings();
457
        }
458
459 55277
        $dbType = strtolower($dbType);
460
461 55277
        return isset($this->doctrineTypeMapping[$dbType]);
462
    }
463
464
    /**
465
     * Initializes the Doctrine Type comments instance variable for in_array() checks.
466
     *
467
     * @return void
468
     */
469 60395
    protected function initializeCommentedDoctrineTypes()
470
    {
471 60395
        $this->doctrineTypeComments = [];
472
473 60395
        foreach (Type::getTypesMap() as $typeName => $className) {
474 60395
            $type = Type::getType($typeName);
475
476 60395
            if (! $type->requiresSQLCommentHint($this)) {
477 60395
                continue;
478
            }
479
480 60395
            $this->doctrineTypeComments[] = $typeName;
481
        }
482 60395
    }
483
484
    /**
485
     * Is it necessary for the platform to add a parsable type comment to allow reverse engineering the given type?
486
     *
487
     * @return bool
488
     */
489 60530
    public function isCommentedDoctrineType(Type $doctrineType)
490
    {
491 60530
        if ($this->doctrineTypeComments === null) {
492 60377
            $this->initializeCommentedDoctrineTypes();
493
        }
494
495 60530
        assert(is_array($this->doctrineTypeComments));
496
497 60530
        return in_array($doctrineType->getName(), $this->doctrineTypeComments);
498
    }
499
500
    /**
501
     * Marks this type as to be commented in ALTER TABLE and CREATE TABLE statements.
502
     *
503
     * @param string|Type $doctrineType
504
     *
505
     * @return void
506
     */
507 53493
    public function markDoctrineTypeCommented($doctrineType)
508
    {
509 53493
        if ($this->doctrineTypeComments === null) {
510 53493
            $this->initializeCommentedDoctrineTypes();
511
        }
512
513 53493
        assert(is_array($this->doctrineTypeComments));
514
515 53493
        $this->doctrineTypeComments[] = $doctrineType instanceof Type ? $doctrineType->getName() : $doctrineType;
516 53493
    }
517
518
    /**
519
     * Gets the comment to append to a column comment that helps parsing this type in reverse engineering.
520
     *
521
     * @return string
522
     */
523 57004
    public function getDoctrineTypeComment(Type $doctrineType)
524
    {
525 57004
        return '(DC2Type:' . $doctrineType->getName() . ')';
526
    }
527
528
    /**
529
     * Gets the comment of a passed column modified by potential doctrine type comment hints.
530
     *
531
     * @return string|null
532
     */
533 60063
    protected function getColumnComment(Column $column)
534
    {
535 60063
        $comment = $column->getComment();
536
537 60063
        if ($this->isCommentedDoctrineType($column->getType())) {
538 57004
            $comment .= $this->getDoctrineTypeComment($column->getType());
539
        }
540
541 60063
        return $comment;
542
    }
543
544
    /**
545
     * Gets the character used for identifier quoting.
546
     *
547
     * @return string
548
     */
549 56190
    public function getIdentifierQuoteCharacter()
550
    {
551 56190
        return '"';
552
    }
553
554
    /**
555
     * Gets the string portion that starts an SQL comment.
556
     *
557
     * @return string
558
     */
559
    public function getSqlCommentStartString()
560
    {
561
        return '--';
562
    }
563
564
    /**
565
     * Gets the string portion that ends an SQL comment.
566
     *
567
     * @return string
568
     */
569
    public function getSqlCommentEndString()
570
    {
571
        return "\n";
572
    }
573
574
    /**
575
     * Gets the maximum length of a char field.
576
     */
577 57685
    public function getCharMaxLength() : int
578
    {
579 57685
        return $this->getVarcharMaxLength();
580
    }
581
582
    /**
583
     * Gets the maximum length of a varchar field.
584
     *
585
     * @return int
586
     */
587 55241
    public function getVarcharMaxLength()
588
    {
589 55241
        return 4000;
590
    }
591
592
    /**
593
     * Gets the default length of a varchar field.
594
     *
595
     * @return int
596
     */
597 56286
    public function getVarcharDefaultLength()
598
    {
599 56286
        return 255;
600
    }
601
602
    /**
603
     * Gets the maximum length of a binary field.
604
     *
605
     * @return int
606
     */
607
    public function getBinaryMaxLength()
608
    {
609
        return 4000;
610
    }
611
612
    /**
613
     * Gets the default length of a binary field.
614
     *
615
     * @return int
616
     */
617 54364
    public function getBinaryDefaultLength()
618
    {
619 54364
        return 255;
620
    }
621
622
    /**
623
     * Gets all SQL wildcard characters of the platform.
624
     *
625
     * @return string[]
626
     */
627
    public function getWildcards()
628
    {
629
        return ['%', '_'];
630
    }
631
632
    /**
633
     * Returns the regular expression operator.
634
     *
635
     * @return string
636
     *
637
     * @throws DBALException If not supported on this platform.
638
     */
639 43728
    public function getRegexpExpression()
640
    {
641 43728
        throw DBALException::notSupported(__METHOD__);
642
    }
643
644
    /**
645
     * Returns the global unique identifier expression.
646
     *
647
     * @deprecated Use application-generated UUIDs instead
648
     *
649
     * @return string
650
     *
651
     * @throws DBALException If not supported on this platform.
652
     */
653
    public function getGuidExpression()
654
    {
655
        throw DBALException::notSupported(__METHOD__);
656
    }
657
658
    /**
659
     * Returns the SQL snippet to get the average value of a column.
660
     *
661
     * @param string $column The column to use.
662
     *
663
     * @return string Generated SQL including an AVG aggregate function.
664
     */
665
    public function getAvgExpression($column)
666
    {
667
        return 'AVG(' . $column . ')';
668
    }
669
670
    /**
671
     * Returns the SQL snippet to get the number of rows (without a NULL value) of a column.
672
     *
673
     * If a '*' is used instead of a column the number of selected rows is returned.
674
     *
675
     * @param string|int $column The column to use.
676
     *
677
     * @return string Generated SQL including a COUNT aggregate function.
678
     */
679
    public function getCountExpression($column)
680
    {
681
        return 'COUNT(' . $column . ')';
682
    }
683
684
    /**
685
     * Returns the SQL snippet to get the highest value of a column.
686
     *
687
     * @param string $column The column to use.
688
     *
689
     * @return string Generated SQL including a MAX aggregate function.
690
     */
691
    public function getMaxExpression($column)
692
    {
693
        return 'MAX(' . $column . ')';
694
    }
695
696
    /**
697
     * Returns the SQL snippet to get the lowest value of a column.
698
     *
699
     * @param string $column The column to use.
700
     *
701
     * @return string Generated SQL including a MIN aggregate function.
702
     */
703
    public function getMinExpression($column)
704
    {
705
        return 'MIN(' . $column . ')';
706
    }
707
708
    /**
709
     * Returns the SQL snippet to get the total sum of a column.
710
     *
711
     * @param string $column The column to use.
712
     *
713
     * @return string Generated SQL including a SUM aggregate function.
714
     */
715
    public function getSumExpression($column)
716
    {
717
        return 'SUM(' . $column . ')';
718
    }
719
720
    // scalar functions
721
722
    /**
723
     * Returns the SQL snippet to get the md5 sum of a field.
724
     *
725
     * Note: Not SQL92, but common functionality.
726
     *
727
     * @param string $column
728
     *
729
     * @return string
730
     */
731
    public function getMd5Expression($column)
732
    {
733
        return 'MD5(' . $column . ')';
734
    }
735
736
    /**
737
     * Returns the SQL snippet to get the length of a text field.
738
     *
739
     * @param string $column
740
     *
741
     * @return string
742
     */
743
    public function getLengthExpression($column)
744
    {
745
        return 'LENGTH(' . $column . ')';
746
    }
747
748
    /**
749
     * Returns the SQL snippet to get the squared value of a column.
750
     *
751
     * @param string $column The column to use.
752
     *
753
     * @return string Generated SQL including an SQRT aggregate function.
754
     */
755
    public function getSqrtExpression($column)
756
    {
757
        return 'SQRT(' . $column . ')';
758
    }
759
760
    /**
761
     * Returns the SQL snippet to round a numeric field to the number of decimals specified.
762
     *
763
     * @param string $column
764
     * @param int    $decimals
765
     *
766
     * @return string
767
     */
768
    public function getRoundExpression($column, $decimals = 0)
769
    {
770
        return 'ROUND(' . $column . ', ' . $decimals . ')';
771
    }
772
773
    /**
774
     * Returns the SQL snippet to get the remainder of the division operation $expression1 / $expression2.
775
     *
776
     * @param string $expression1
777
     * @param string $expression2
778
     *
779
     * @return string
780
     */
781
    public function getModExpression($expression1, $expression2)
782
    {
783
        return 'MOD(' . $expression1 . ', ' . $expression2 . ')';
784
    }
785
786
    /**
787
     * Returns the SQL snippet to trim a string.
788
     *
789
     * @param string      $str  The expression to apply the trim to.
790
     * @param int         $mode The position of the trim (leading/trailing/both).
791
     * @param string|bool $char The char to trim, has to be quoted already. Defaults to space.
792
     *
793
     * @return string
794
     */
795 51382
    public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false)
796
    {
797 51382
        $expression = '';
798
799 51382
        switch ($mode) {
800
            case TrimMode::LEADING:
801 51335
                $expression = 'LEADING ';
802 51335
                break;
803
804
            case TrimMode::TRAILING:
805 51315
                $expression = 'TRAILING ';
806 51315
                break;
807
808
            case TrimMode::BOTH:
809 51295
                $expression = 'BOTH ';
810 51295
                break;
811
        }
812
813 51382
        if ($char !== false) {
814 51294
            $expression .= $char . ' ';
815
        }
816
817 51382
        if ($mode || $char !== false) {
818 51360
            $expression .= 'FROM ';
819
        }
820
821 51382
        return 'TRIM(' . $expression . $str . ')';
822
    }
823
824
    /**
825
     * Returns the SQL snippet to trim trailing space characters from the expression.
826
     *
827
     * @param string $str Literal string or column name.
828
     *
829
     * @return string
830
     */
831 25764
    public function getRtrimExpression($str)
832
    {
833 25764
        return 'RTRIM(' . $str . ')';
834
    }
835
836
    /**
837
     * Returns the SQL snippet to trim leading space characters from the expression.
838
     *
839
     * @param string $str Literal string or column name.
840
     *
841
     * @return string
842
     */
843 25764
    public function getLtrimExpression($str)
844
    {
845 25764
        return 'LTRIM(' . $str . ')';
846
    }
847
848
    /**
849
     * Returns the SQL snippet to change all characters from the expression to uppercase,
850
     * according to the current character set mapping.
851
     *
852
     * @param string $str Literal string or column name.
853
     *
854
     * @return string
855
     */
856
    public function getUpperExpression($str)
857
    {
858
        return 'UPPER(' . $str . ')';
859
    }
860
861
    /**
862
     * Returns the SQL snippet to change all characters from the expression to lowercase,
863
     * according to the current character set mapping.
864
     *
865
     * @param string $str Literal string or column name.
866
     *
867
     * @return string
868
     */
869
    public function getLowerExpression($str)
870
    {
871
        return 'LOWER(' . $str . ')';
872
    }
873
874
    /**
875
     * Returns the SQL snippet to get the position of the first occurrence of substring $substr in string $str.
876
     *
877
     * @param string    $str      Literal string.
878
     * @param string    $substr   Literal string to find.
879
     * @param int|false $startPos Position to start at, beginning of string by default.
880
     *
881
     * @return string
882
     *
883
     * @throws DBALException If not supported on this platform.
884
     */
885
    public function getLocateExpression($str, $substr, $startPos = false)
886
    {
887
        throw DBALException::notSupported(__METHOD__);
888
    }
889
890
    /**
891
     * Returns the SQL snippet to get the current system date.
892
     *
893
     * @return string
894
     */
895
    public function getNowExpression()
896
    {
897
        return 'NOW()';
898
    }
899
900
    /**
901
     * Returns a SQL snippet to get a substring inside an SQL statement.
902
     *
903
     * Note: Not SQL92, but common functionality.
904
     *
905
     * SQLite only supports the 2 parameter variant of this function.
906
     *
907
     * @param string   $value  An sql string literal or column name/alias.
908
     * @param int      $from   Where to start the substring portion.
909
     * @param int|null $length The substring portion length.
910
     *
911
     * @return string
912
     */
913
    public function getSubstringExpression($value, $from, $length = null)
914
    {
915
        if ($length === null) {
916
            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
917
        }
918
919
        return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')';
920
    }
921
922
    /**
923
     * Returns a SQL snippet to concatenate the given expressions.
924
     *
925
     * Accepts an arbitrary number of string parameters. Each parameter must contain an expression.
926
     *
927
     * @return string
928
     */
929 43706
    public function getConcatExpression()
930
    {
931 43706
        return implode(' || ', func_get_args());
932
    }
933
934
    /**
935
     * Returns the SQL for a logical not.
936
     *
937
     * Example:
938
     * <code>
939
     * $q = new Doctrine_Query();
940
     * $e = $q->expr;
941
     * $q->select('*')->from('table')
942
     *   ->where($e->eq('id', $e->not('null'));
943
     * </code>
944
     *
945
     * @param string $expression
946
     *
947
     * @return string The logical expression.
948
     */
949
    public function getNotExpression($expression)
950
    {
951
        return 'NOT(' . $expression . ')';
952
    }
953
954
    /**
955
     * Returns the SQL that checks if an expression is null.
956
     *
957
     * @param string $expression The expression that should be compared to null.
958
     *
959
     * @return string The logical expression.
960
     */
961 60284
    public function getIsNullExpression($expression)
962
    {
963 60284
        return $expression . ' IS NULL';
964
    }
965
966
    /**
967
     * Returns the SQL that checks if an expression is not null.
968
     *
969
     * @param string $expression The expression that should be compared to null.
970
     *
971
     * @return string The logical expression.
972
     */
973
    public function getIsNotNullExpression($expression)
974
    {
975
        return $expression . ' IS NOT NULL';
976
    }
977
978
    /**
979
     * Returns the SQL that checks if an expression evaluates to a value between two values.
980
     *
981
     * The parameter $expression is checked if it is between $value1 and $value2.
982
     *
983
     * Note: There is a slight difference in the way BETWEEN works on some databases.
984
     * http://www.w3schools.com/sql/sql_between.asp. If you want complete database
985
     * independence you should avoid using between().
986
     *
987
     * @param string $expression The value to compare to.
988
     * @param string $value1     The lower value to compare with.
989
     * @param string $value2     The higher value to compare with.
990
     *
991
     * @return string The logical expression.
992
     */
993
    public function getBetweenExpression($expression, $value1, $value2)
994
    {
995
        return $expression . ' BETWEEN ' . $value1 . ' AND ' . $value2;
996
    }
997
998
    /**
999
     * Returns the SQL to get the arccosine of a value.
1000
     *
1001
     * @param string $value
1002
     *
1003
     * @return string
1004
     */
1005
    public function getAcosExpression($value)
1006
    {
1007
        return 'ACOS(' . $value . ')';
1008
    }
1009
1010
    /**
1011
     * Returns the SQL to get the sine of a value.
1012
     *
1013
     * @param string $value
1014
     *
1015
     * @return string
1016
     */
1017
    public function getSinExpression($value)
1018
    {
1019
        return 'SIN(' . $value . ')';
1020
    }
1021
1022
    /**
1023
     * Returns the SQL to get the PI value.
1024
     *
1025
     * @return string
1026
     */
1027
    public function getPiExpression()
1028
    {
1029
        return 'PI()';
1030
    }
1031
1032
    /**
1033
     * Returns the SQL to get the cosine of a value.
1034
     *
1035
     * @param string $value
1036
     *
1037
     * @return string
1038
     */
1039
    public function getCosExpression($value)
1040
    {
1041
        return 'COS(' . $value . ')';
1042
    }
1043
1044
    /**
1045
     * Returns the SQL to calculate the difference in days between the two passed dates.
1046
     *
1047
     * Computes diff = date1 - date2.
1048
     *
1049
     * @param string $date1
1050
     * @param string $date2
1051
     *
1052
     * @return string
1053
     *
1054
     * @throws DBALException If not supported on this platform.
1055
     */
1056
    public function getDateDiffExpression($date1, $date2)
1057
    {
1058
        throw DBALException::notSupported(__METHOD__);
1059
    }
1060
1061
    /**
1062
     * Returns the SQL to add the number of given seconds to a date.
1063
     *
1064
     * @param string $date
1065
     * @param int    $seconds
1066
     *
1067
     * @return string
1068
     *
1069
     * @throws DBALException If not supported on this platform.
1070
     */
1071 58620
    public function getDateAddSecondsExpression($date, $seconds)
1072
    {
1073 58620
        return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, DateIntervalUnit::SECOND);
1074
    }
1075
1076
    /**
1077
     * Returns the SQL to subtract the number of given seconds from a date.
1078
     *
1079
     * @param string $date
1080
     * @param int    $seconds
1081
     *
1082
     * @return string
1083
     *
1084
     * @throws DBALException If not supported on this platform.
1085
     */
1086 58620
    public function getDateSubSecondsExpression($date, $seconds)
1087
    {
1088 58620
        return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, DateIntervalUnit::SECOND);
1089
    }
1090
1091
    /**
1092
     * Returns the SQL to add the number of given minutes to a date.
1093
     *
1094
     * @param string $date
1095
     * @param int    $minutes
1096
     *
1097
     * @return string
1098
     *
1099
     * @throws DBALException If not supported on this platform.
1100
     */
1101 58620
    public function getDateAddMinutesExpression($date, $minutes)
1102
    {
1103 58620
        return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, DateIntervalUnit::MINUTE);
1104
    }
1105
1106
    /**
1107
     * Returns the SQL to subtract the number of given minutes from a date.
1108
     *
1109
     * @param string $date
1110
     * @param int    $minutes
1111
     *
1112
     * @return string
1113
     *
1114
     * @throws DBALException If not supported on this platform.
1115
     */
1116 58620
    public function getDateSubMinutesExpression($date, $minutes)
1117
    {
1118 58620
        return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, DateIntervalUnit::MINUTE);
1119
    }
1120
1121
    /**
1122
     * Returns the SQL to add the number of given hours to a date.
1123
     *
1124
     * @param string $date
1125
     * @param int    $hours
1126
     *
1127
     * @return string
1128
     *
1129
     * @throws DBALException If not supported on this platform.
1130
     */
1131 58620
    public function getDateAddHourExpression($date, $hours)
1132
    {
1133 58620
        return $this->getDateArithmeticIntervalExpression($date, '+', $hours, DateIntervalUnit::HOUR);
1134
    }
1135
1136
    /**
1137
     * Returns the SQL to subtract the number of given hours to a date.
1138
     *
1139
     * @param string $date
1140
     * @param int    $hours
1141
     *
1142
     * @return string
1143
     *
1144
     * @throws DBALException If not supported on this platform.
1145
     */
1146 58620
    public function getDateSubHourExpression($date, $hours)
1147
    {
1148 58620
        return $this->getDateArithmeticIntervalExpression($date, '-', $hours, DateIntervalUnit::HOUR);
1149
    }
1150
1151
    /**
1152
     * Returns the SQL to add the number of given days to a date.
1153
     *
1154
     * @param string $date
1155
     * @param int    $days
1156
     *
1157
     * @return string
1158
     *
1159
     * @throws DBALException If not supported on this platform.
1160
     */
1161 58622
    public function getDateAddDaysExpression($date, $days)
1162
    {
1163 58622
        return $this->getDateArithmeticIntervalExpression($date, '+', $days, DateIntervalUnit::DAY);
1164
    }
1165
1166
    /**
1167
     * Returns the SQL to subtract the number of given days to a date.
1168
     *
1169
     * @param string $date
1170
     * @param int    $days
1171
     *
1172
     * @return string
1173
     *
1174
     * @throws DBALException If not supported on this platform.
1175
     */
1176 58620
    public function getDateSubDaysExpression($date, $days)
1177
    {
1178 58620
        return $this->getDateArithmeticIntervalExpression($date, '-', $days, DateIntervalUnit::DAY);
1179
    }
1180
1181
    /**
1182
     * Returns the SQL to add the number of given weeks to a date.
1183
     *
1184
     * @param string $date
1185
     * @param int    $weeks
1186
     *
1187
     * @return string
1188
     *
1189
     * @throws DBALException If not supported on this platform.
1190
     */
1191 58620
    public function getDateAddWeeksExpression($date, $weeks)
1192
    {
1193 58620
        return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, DateIntervalUnit::WEEK);
1194
    }
1195
1196
    /**
1197
     * Returns the SQL to subtract the number of given weeks from a date.
1198
     *
1199
     * @param string $date
1200
     * @param int    $weeks
1201
     *
1202
     * @return string
1203
     *
1204
     * @throws DBALException If not supported on this platform.
1205
     */
1206 58620
    public function getDateSubWeeksExpression($date, $weeks)
1207
    {
1208 58620
        return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, DateIntervalUnit::WEEK);
1209
    }
1210
1211
    /**
1212
     * Returns the SQL to add the number of given months to a date.
1213
     *
1214
     * @param string $date
1215
     * @param int    $months
1216
     *
1217
     * @return string
1218
     *
1219
     * @throws DBALException If not supported on this platform.
1220
     */
1221 58620
    public function getDateAddMonthExpression($date, $months)
1222
    {
1223 58620
        return $this->getDateArithmeticIntervalExpression($date, '+', $months, DateIntervalUnit::MONTH);
1224
    }
1225
1226
    /**
1227
     * Returns the SQL to subtract the number of given months to a date.
1228
     *
1229
     * @param string $date
1230
     * @param int    $months
1231
     *
1232
     * @return string
1233
     *
1234
     * @throws DBALException If not supported on this platform.
1235
     */
1236 58620
    public function getDateSubMonthExpression($date, $months)
1237
    {
1238 58620
        return $this->getDateArithmeticIntervalExpression($date, '-', $months, DateIntervalUnit::MONTH);
1239
    }
1240
1241
    /**
1242
     * Returns the SQL to add the number of given quarters to a date.
1243
     *
1244
     * @param string $date
1245
     * @param int    $quarters
1246
     *
1247
     * @return string
1248
     *
1249
     * @throws DBALException If not supported on this platform.
1250
     */
1251 58620
    public function getDateAddQuartersExpression($date, $quarters)
1252
    {
1253 58620
        return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, DateIntervalUnit::QUARTER);
1254
    }
1255
1256
    /**
1257
     * Returns the SQL to subtract the number of given quarters from a date.
1258
     *
1259
     * @param string $date
1260
     * @param int    $quarters
1261
     *
1262
     * @return string
1263
     *
1264
     * @throws DBALException If not supported on this platform.
1265
     */
1266 58620
    public function getDateSubQuartersExpression($date, $quarters)
1267
    {
1268 58620
        return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, DateIntervalUnit::QUARTER);
1269
    }
1270
1271
    /**
1272
     * Returns the SQL to add the number of given years to a date.
1273
     *
1274
     * @param string $date
1275
     * @param int    $years
1276
     *
1277
     * @return string
1278
     *
1279
     * @throws DBALException If not supported on this platform.
1280
     */
1281 58620
    public function getDateAddYearsExpression($date, $years)
1282
    {
1283 58620
        return $this->getDateArithmeticIntervalExpression($date, '+', $years, DateIntervalUnit::YEAR);
1284
    }
1285
1286
    /**
1287
     * Returns the SQL to subtract the number of given years from a date.
1288
     *
1289
     * @param string $date
1290
     * @param int    $years
1291
     *
1292
     * @return string
1293
     *
1294
     * @throws DBALException If not supported on this platform.
1295
     */
1296 58620
    public function getDateSubYearsExpression($date, $years)
1297
    {
1298 58620
        return $this->getDateArithmeticIntervalExpression($date, '-', $years, DateIntervalUnit::YEAR);
1299
    }
1300
1301
    /**
1302
     * Returns the SQL for a date arithmetic expression.
1303
     *
1304
     * @param string $date     The column or literal representing a date to perform the arithmetic operation on.
1305
     * @param string $operator The arithmetic operator (+ or -).
1306
     * @param int    $interval The interval that shall be calculated into the date.
1307
     * @param string $unit     The unit of the interval that shall be calculated into the date.
1308
     *                         One of the DATE_INTERVAL_UNIT_* constants.
1309
     *
1310
     * @return string
1311
     *
1312
     * @throws DBALException If not supported on this platform.
1313
     */
1314
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
1315
    {
1316
        throw DBALException::notSupported(__METHOD__);
1317
    }
1318
1319
    /**
1320
     * Returns the SQL bit AND comparison expression.
1321
     *
1322
     * @param string $value1
1323
     * @param string $value2
1324
     *
1325
     * @return string
1326
     */
1327 58296
    public function getBitAndComparisonExpression($value1, $value2)
1328
    {
1329 58296
        return '(' . $value1 . ' & ' . $value2 . ')';
1330
    }
1331
1332
    /**
1333
     * Returns the SQL bit OR comparison expression.
1334
     *
1335
     * @param string $value1
1336
     * @param string $value2
1337
     *
1338
     * @return string
1339
     */
1340 56098
    public function getBitOrComparisonExpression($value1, $value2)
1341
    {
1342 56098
        return '(' . $value1 . ' | ' . $value2 . ')';
1343
    }
1344
1345
    /**
1346
     * Returns the FOR UPDATE expression.
1347
     *
1348
     * @return string
1349
     */
1350 45668
    public function getForUpdateSQL()
1351
    {
1352 45668
        return 'FOR UPDATE';
1353
    }
1354
1355
    /**
1356
     * Honors that some SQL vendors such as MsSql use table hints for locking instead of the ANSI SQL FOR UPDATE specification.
1357
     *
1358
     * @param string   $fromClause The FROM clause to append the hint for the given lock mode to.
1359
     * @param int|null $lockMode   One of the Doctrine\DBAL\LockMode::* constants. If null is given, nothing will
1360
     *                             be appended to the FROM clause.
1361
     *
1362
     * @return string
1363
     */
1364 48071
    public function appendLockHint($fromClause, $lockMode)
1365
    {
1366 48071
        return $fromClause;
1367
    }
1368
1369
    /**
1370
     * Returns the SQL snippet to append to any SELECT statement which locks rows in shared read lock.
1371
     *
1372
     * This defaults to the ANSI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database
1373
     * vendors allow to lighten this constraint up to be a real read lock.
1374
     *
1375
     * @return string
1376
     */
1377
    public function getReadLockSQL()
1378
    {
1379
        return $this->getForUpdateSQL();
1380
    }
1381
1382
    /**
1383
     * Returns the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows.
1384
     *
1385
     * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ANSI SQL standard.
1386
     *
1387
     * @return string
1388
     */
1389 52878
    public function getWriteLockSQL()
1390
    {
1391 52878
        return $this->getForUpdateSQL();
1392
    }
1393
1394
    /**
1395
     * Returns the SQL snippet to drop an existing database.
1396
     *
1397
     * @param string $database The name of the database that should be dropped.
1398
     *
1399
     * @return string
1400
     */
1401 46728
    public function getDropDatabaseSQL($database)
1402
    {
1403 46728
        return 'DROP DATABASE ' . $database;
1404
    }
1405
1406
    /**
1407
     * Returns the SQL snippet to drop an existing table.
1408
     *
1409
     * @param Table|string $table
1410
     *
1411
     * @return string
1412
     *
1413
     * @throws InvalidArgumentException
1414
     */
1415 59650
    public function getDropTableSQL($table)
1416
    {
1417 59650
        $tableArg = $table;
1418
1419 59650
        if ($table instanceof Table) {
1420 6737
            $table = $table->getQuotedName($this);
1421
        }
1422
1423 59650
        if (! is_string($table)) {
1424
            throw new InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
1425
        }
1426
1427 59650
        if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaDropTable)) {
1428 53148
            $eventArgs = new SchemaDropTableEventArgs($tableArg, $this);
1429 53148
            $this->_eventManager->dispatchEvent(Events::onSchemaDropTable, $eventArgs);
1430
1431 53148
            if ($eventArgs->isDefaultPrevented()) {
1432
                $sql = $eventArgs->getSql();
1433
1434
                if ($sql === null) {
1435
                    throw new UnexpectedValueException('Default implementation of DROP TABLE was overridden with NULL');
1436
                }
1437
1438
                return $sql;
1439
            }
1440
        }
1441
1442 59650
        return 'DROP TABLE ' . $table;
1443
    }
1444
1445
    /**
1446
     * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction.
1447
     *
1448
     * @param Table|string $table
1449
     *
1450
     * @return string
1451
     */
1452 26425
    public function getDropTemporaryTableSQL($table)
1453
    {
1454 26425
        return $this->getDropTableSQL($table);
1455
    }
1456
1457
    /**
1458
     * Returns the SQL to drop an index from a table.
1459
     *
1460
     * @param Index|string $index
1461
     * @param Table|string $table
1462
     *
1463
     * @return string
1464
     *
1465
     * @throws InvalidArgumentException
1466
     */
1467 46637
    public function getDropIndexSQL($index, $table = null)
1468
    {
1469 46637
        if ($index instanceof Index) {
1470 46607
            $index = $index->getQuotedName($this);
1471 22169
        } elseif (! is_string($index)) {
1472
            throw new InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
1473
        }
1474
1475 46637
        return 'DROP INDEX ' . $index;
1476
    }
1477
1478
    /**
1479
     * Returns the SQL to drop a constraint.
1480
     *
1481
     * @param Constraint|string $constraint
1482
     * @param Table|string      $table
1483
     *
1484
     * @return string
1485
     */
1486 53825
    public function getDropConstraintSQL($constraint, $table)
1487
    {
1488 53825
        if (! $constraint instanceof Constraint) {
1489 52586
            $constraint = new Identifier($constraint);
1490
        }
1491
1492 53825
        if (! $table instanceof Table) {
1493 53825
            $table = new Identifier($table);
1494
        }
1495
1496 53825
        $constraint = $constraint->getQuotedName($this);
1497 53825
        $table      = $table->getQuotedName($this);
1498
1499 53825
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint;
1500
    }
1501
1502
    /**
1503
     * Returns the SQL to drop a foreign key.
1504
     *
1505
     * @param ForeignKeyConstraint|string $foreignKey
1506
     * @param Table|string                $table
1507
     *
1508
     * @return string
1509
     */
1510 54731
    public function getDropForeignKeySQL($foreignKey, $table)
1511
    {
1512 54731
        if (! $foreignKey instanceof ForeignKeyConstraint) {
1513 52471
            $foreignKey = new Identifier($foreignKey);
1514
        }
1515
1516 54731
        if (! $table instanceof Table) {
1517 54731
            $table = new Identifier($table);
1518
        }
1519
1520 54731
        $foreignKey = $foreignKey->getQuotedName($this);
1521 54731
        $table      = $table->getQuotedName($this);
1522
1523 54731
        return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey;
1524
    }
1525
1526
    /**
1527
     * Returns the SQL statement(s) to create a table with the specified name, columns and constraints
1528
     * on this platform.
1529
     *
1530
     * @param int $createFlags
1531
     *
1532
     * @return string[] The sequence of SQL statements.
1533
     *
1534
     * @throws DBALException
1535
     * @throws InvalidArgumentException
1536
     */
1537 59920
    public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES)
1538
    {
1539 59920
        if (! is_int($createFlags)) {
1540
            throw new InvalidArgumentException('Second argument of AbstractPlatform::getCreateTableSQL() has to be integer.');
1541
        }
1542
1543 59920
        if (count($table->getColumns()) === 0) {
1544 53401
            throw DBALException::noColumnsSpecifiedForTable($table->getName());
1545
        }
1546
1547 59902
        $tableName                    = $table->getQuotedName($this);
1548 59902
        $options                      = $table->getOptions();
1549 59902
        $options['uniqueConstraints'] = [];
1550 59902
        $options['indexes']           = [];
1551 59902
        $options['primary']           = [];
1552
1553 59902
        if (($createFlags&self::CREATE_INDEXES) > 0) {
1554 59879
            foreach ($table->getIndexes() as $index) {
1555 59749
                if ($index->isPrimary()) {
1556 59683
                    $options['primary']       = $index->getQuotedColumns($this);
1557 59683
                    $options['primary_index'] = $index;
1558
                } else {
1559 58488
                    $options['indexes'][$index->getQuotedName($this)] = $index;
1560
                }
1561
            }
1562
        }
1563
1564 59902
        $columnSql = [];
1565 59902
        $columns   = [];
1566
1567 59902
        foreach ($table->getColumns() as $column) {
1568 59902
            if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn)) {
1569 53171
                $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this);
1570 53171
                $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs);
1571
1572 53171
                $columnSql = array_merge($columnSql, $eventArgs->getSql());
1573
1574 53171
                if ($eventArgs->isDefaultPrevented()) {
1575
                    continue;
1576
                }
1577
            }
1578
1579 59902
            $columnData            = $column->toArray();
1580 59902
            $columnData['name']    = $column->getQuotedName($this);
1581 59902
            $columnData['version'] = $column->hasPlatformOption('version') ? $column->getPlatformOption('version') : false;
1582 59902
            $columnData['comment'] = $this->getColumnComment($column);
1583
1584 59902
            if ($columnData['type'] instanceof Types\StringType && $columnData['length'] === null) {
1585 59287
                $columnData['length'] = 255;
1586
            }
1587
1588 59902
            if (in_array($column->getName(), $options['primary'])) {
1589 59663
                $columnData['primary'] = true;
1590
            }
1591
1592 59902
            $columns[$columnData['name']] = $columnData;
1593
        }
1594
1595 59902
        if (($createFlags&self::CREATE_FOREIGNKEYS) > 0) {
1596 59674
            $options['foreignKeys'] = [];
1597 59674
            foreach ($table->getForeignKeys() as $fkConstraint) {
1598 58395
                $options['foreignKeys'][] = $fkConstraint;
1599
            }
1600
        }
1601
1602 59902
        if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) {
1603 53171
            $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this);
1604 53171
            $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs);
1605
1606 53171
            if ($eventArgs->isDefaultPrevented()) {
1607
                return array_merge($eventArgs->getSql(), $columnSql);
1608
            }
1609
        }
1610
1611 59902
        $sql = $this->_getCreateTableSQL($tableName, $columns, $options);
1612 59902
        if ($this->supportsCommentOnStatement()) {
1613 56468
            if ($table->hasOption('comment')) {
1614 38571
                $sql[] = $this->getCommentOnTableSQL($tableName, $table->getOption('comment'));
1615
            }
1616
1617 56468
            foreach ($table->getColumns() as $column) {
1618 56468
                $comment = $this->getColumnComment($column);
1619
1620 56468
                if ($comment === null || $comment === '') {
1621 56451
                    continue;
1622
                }
1623
1624 54474
                $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment);
1625
            }
1626
        }
1627
1628 59902
        return array_merge($sql, $columnSql);
1629
    }
1630
1631 38571
    protected function getCommentOnTableSQL(string $tableName, ?string $comment) : string
1632
    {
1633 38571
        $tableName = new Identifier($tableName);
1634
1635 38571
        return sprintf(
1636 2
            'COMMENT ON TABLE %s IS %s',
1637 38571
            $tableName->getQuotedName($this),
1638 38571
            $this->quoteStringLiteral((string) $comment)
1639
        );
1640
    }
1641
1642
    /**
1643
     * @param string      $tableName
1644
     * @param string      $columnName
1645
     * @param string|null $comment
1646
     *
1647
     * @return string
1648
     */
1649 53317
    public function getCommentOnColumnSQL($tableName, $columnName, $comment)
1650
    {
1651 53317
        $tableName  = new Identifier($tableName);
1652 53317
        $columnName = new Identifier($columnName);
1653
1654 53317
        return sprintf(
1655 66
            'COMMENT ON COLUMN %s.%s IS %s',
1656 53317
            $tableName->getQuotedName($this),
1657 53317
            $columnName->getQuotedName($this),
1658 53317
            $this->quoteStringLiteral((string) $comment)
1659
        );
1660
    }
1661
1662
    /**
1663
     * Returns the SQL to create inline comment on a column.
1664
     *
1665
     * @param string $comment
1666
     *
1667
     * @return string
1668
     *
1669
     * @throws DBALException If not supported on this platform.
1670
     */
1671 54835
    public function getInlineColumnCommentSQL($comment)
1672
    {
1673 54835
        if (! $this->supportsInlineColumnComments()) {
1674 52362
            throw DBALException::notSupported(__METHOD__);
1675
        }
1676
1677 53897
        return 'COMMENT ' . $this->quoteStringLiteral($comment);
1678
    }
1679
1680
    /**
1681
     * Returns the SQL used to create a table.
1682
     *
1683
     * @param string    $tableName
1684
     * @param mixed[][] $columns
1685
     * @param mixed[]   $options
1686
     *
1687
     * @return string[]
1688
     */
1689 54726
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
1690
    {
1691 54726
        $columnListSql = $this->getColumnDeclarationListSQL($columns);
1692
1693 54726
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
1694
            foreach ($options['uniqueConstraints'] as $name => $definition) {
1695
                $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
1696
            }
1697
        }
1698
1699 54726
        if (isset($options['primary']) && ! empty($options['primary'])) {
1700 54660
            $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')';
1701
        }
1702
1703 54726
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
1704
            foreach ($options['indexes'] as $index => $definition) {
1705
                $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition);
1706
            }
1707
        }
1708
1709 54726
        $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;
1710
1711 54726
        $check = $this->getCheckDeclarationSQL($columns);
1712 54726
        if (! empty($check)) {
1713 54327
            $query .= ', ' . $check;
1714
        }
1715
1716 54726
        $query .= ')';
1717
1718 54726
        $sql[] = $query;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$sql was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sql = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1719
1720 54726
        if (isset($options['foreignKeys'])) {
1721 54673
            foreach ((array) $options['foreignKeys'] as $definition) {
1722 54463
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
1723
            }
1724
        }
1725
1726 54726
        return $sql;
1727
    }
1728
1729
    /**
1730
     * @return string
1731
     */
1732 48030
    public function getCreateTemporaryTableSnippetSQL()
1733
    {
1734 48030
        return 'CREATE TEMPORARY TABLE';
1735
    }
1736
1737
    /**
1738
     * Returns the SQL to create a sequence on this platform.
1739
     *
1740
     * @return string
1741
     *
1742
     * @throws DBALException If not supported on this platform.
1743
     */
1744
    public function getCreateSequenceSQL(Sequence $sequence)
1745
    {
1746
        throw DBALException::notSupported(__METHOD__);
1747
    }
1748
1749
    /**
1750
     * Returns the SQL to change a sequence on this platform.
1751
     *
1752
     * @return string
1753
     *
1754
     * @throws DBALException If not supported on this platform.
1755
     */
1756
    public function getAlterSequenceSQL(Sequence $sequence)
1757
    {
1758
        throw DBALException::notSupported(__METHOD__);
1759
    }
1760
1761
    /**
1762
     * Returns the SQL to create a constraint on a table on this platform.
1763
     *
1764
     * @param Table|string $table
1765
     *
1766
     * @return string
1767
     *
1768
     * @throws InvalidArgumentException
1769
     */
1770 53286
    public function getCreateConstraintSQL(Constraint $constraint, $table)
1771
    {
1772 53286
        if ($table instanceof Table) {
1773
            $table = $table->getQuotedName($this);
1774
        }
1775
1776 53286
        $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this);
1777
1778 53286
        $columnList = '(' . implode(', ', $constraint->getQuotedColumns($this)) . ')';
1779
1780 53286
        $referencesClause = '';
1781 53286
        if ($constraint instanceof Index) {
1782 53286
            if ($constraint->isPrimary()) {
1783 53286
                $query .= ' PRIMARY KEY';
1784 53258
            } elseif ($constraint->isUnique()) {
1785 53258
                $query .= ' UNIQUE';
1786
            } else {
1787
                throw new InvalidArgumentException(
1788 53286
                    'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().'
1789
                );
1790
            }
1791 53258
        } elseif ($constraint instanceof ForeignKeyConstraint) {
1792 53258
            $query .= ' FOREIGN KEY';
1793
1794 53258
            $referencesClause = ' REFERENCES ' . $constraint->getQuotedForeignTableName($this) .
1795 53258
                ' (' . implode(', ', $constraint->getQuotedForeignColumns($this)) . ')';
1796
        }
1797
1798 53286
        $query .= ' ' . $columnList . $referencesClause;
1799
1800 53286
        return $query;
1801
    }
1802
1803
    /**
1804
     * Returns the SQL to create an index on a table on this platform.
1805
     *
1806
     * @param Table|string $table The name of the table on which the index is to be created.
1807
     *
1808
     * @return string
1809
     *
1810
     * @throws InvalidArgumentException
1811
     */
1812 57517
    public function getCreateIndexSQL(Index $index, $table)
1813
    {
1814 57517
        if ($table instanceof Table) {
1815 56634
            $table = $table->getQuotedName($this);
1816
        }
1817
1818 57517
        $name    = $index->getQuotedName($this);
1819 57517
        $columns = $index->getColumns();
1820
1821 57517
        if (count($columns) === 0) {
1822
            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
1823
        }
1824
1825 57517
        if ($index->isPrimary()) {
1826 51633
            return $this->getCreatePrimaryKeySQL($index, $table);
1827
        }
1828
1829 57498
        $query  = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table;
1830 57498
        $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index);
1831
1832 57498
        return $query;
1833
    }
1834
1835
    /**
1836
     * Adds condition for partial index.
1837
     *
1838
     * @return string
1839
     */
1840 58593
    protected function getPartialIndexSQL(Index $index)
1841
    {
1842 58593
        if ($this->supportsPartialIndexes() && $index->hasOption('where')) {
1843 44744
            return ' WHERE ' . $index->getOption('where');
1844
        }
1845
1846 58588
        return '';
1847
    }
1848
1849
    /**
1850
     * Adds additional flags for index generation.
1851
     *
1852
     * @return string
1853
     */
1854 56024
    protected function getCreateIndexSQLFlags(Index $index)
1855
    {
1856 56024
        return $index->isUnique() ? 'UNIQUE ' : '';
1857
    }
1858
1859
    /**
1860
     * Returns the SQL to create an unnamed primary key constraint.
1861
     *
1862
     * @param Table|string $table
1863
     *
1864
     * @return string
1865
     */
1866 54251
    public function getCreatePrimaryKeySQL(Index $index, $table)
1867
    {
1868 54251
        if ($table instanceof Table) {
1869
            $table = $table->getQuotedName($this);
1870
        }
1871
1872 54251
        return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')';
1873
    }
1874
1875
    /**
1876
     * Returns the SQL to create a named schema.
1877
     *
1878
     * @param string $schemaName
1879
     *
1880
     * @return string
1881
     *
1882
     * @throws DBALException If not supported on this platform.
1883
     */
1884 52726
    public function getCreateSchemaSQL($schemaName)
1885
    {
1886 52726
        throw DBALException::notSupported(__METHOD__);
1887
    }
1888
1889
    /**
1890
     * Quotes a string so that it can be safely used as a table or column name,
1891
     * even if it is a reserved word of the platform. This also detects identifier
1892
     * chains separated by dot and quotes them independently.
1893
     *
1894
     * NOTE: Just because you CAN use quoted identifiers doesn't mean
1895
     * you SHOULD use them. In general, they end up causing way more
1896
     * problems than they solve.
1897
     *
1898
     * @param string $str The identifier name to be quoted.
1899
     *
1900
     * @return string The quoted identifier string.
1901
     */
1902 59539
    public function quoteIdentifier($str)
1903
    {
1904 59539
        if (strpos($str, '.') !== false) {
1905 54456
            $parts = array_map([$this, 'quoteSingleIdentifier'], explode('.', $str));
1906
1907 54456
            return implode('.', $parts);
1908
        }
1909
1910 59539
        return $this->quoteSingleIdentifier($str);
1911
    }
1912
1913
    /**
1914
     * Quotes a single identifier (no dot chain separation).
1915
     *
1916
     * @param string $str The identifier name to be quoted.
1917
     *
1918
     * @return string The quoted identifier string.
1919
     */
1920 59369
    public function quoteSingleIdentifier($str)
1921
    {
1922 59369
        $c = $this->getIdentifierQuoteCharacter();
1923
1924 59369
        return $c . str_replace($c, $c . $c, $str) . $c;
1925
    }
1926
1927
    /**
1928
     * Returns the SQL to create a new foreign key.
1929
     *
1930
     * @param ForeignKeyConstraint $foreignKey The foreign key constraint.
1931
     * @param Table|string         $table      The name of the table on which the foreign key is to be created.
1932
     *
1933
     * @return string
1934
     */
1935 58300
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
1936
    {
1937 58300
        if ($table instanceof Table) {
1938 54952
            $table = $table->getQuotedName($this);
1939
        }
1940
1941 58300
        return 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey);
1942
    }
1943
1944
    /**
1945
     * Gets the SQL statements for altering an existing table.
1946
     *
1947
     * This method returns an array of SQL statements, since some platforms need several statements.
1948
     *
1949
     * @return string[]
1950
     *
1951
     * @throws DBALException If not supported on this platform.
1952
     */
1953
    public function getAlterTableSQL(TableDiff $diff)
1954
    {
1955
        throw DBALException::notSupported(__METHOD__);
1956
    }
1957
1958
    /**
1959
     * @param mixed[] $columnSql
1960
     *
1961
     * @return bool
1962
     */
1963 57407
    protected function onSchemaAlterTableAddColumn(Column $column, TableDiff $diff, &$columnSql)
1964
    {
1965 57407
        if ($this->_eventManager === null) {
1966 53279
            return false;
1967
        }
1968
1969 57327
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableAddColumn)) {
1970 57309
            return false;
1971
        }
1972
1973 53125
        $eventArgs = new SchemaAlterTableAddColumnEventArgs($column, $diff, $this);
1974 53125
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableAddColumn, $eventArgs);
1975
1976 53125
        $columnSql = array_merge($columnSql, $eventArgs->getSql());
1977
1978 53125
        return $eventArgs->isDefaultPrevented();
1979
    }
1980
1981
    /**
1982
     * @param string[] $columnSql
1983
     *
1984
     * @return bool
1985
     */
1986 56666
    protected function onSchemaAlterTableRemoveColumn(Column $column, TableDiff $diff, &$columnSql)
1987
    {
1988 56666
        if ($this->_eventManager === null) {
1989 53256
            return false;
1990
        }
1991
1992 56609
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRemoveColumn)) {
1993 56591
            return false;
1994
        }
1995
1996 53125
        $eventArgs = new SchemaAlterTableRemoveColumnEventArgs($column, $diff, $this);
1997 53125
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRemoveColumn, $eventArgs);
1998
1999 53125
        $columnSql = array_merge($columnSql, $eventArgs->getSql());
2000
2001 53125
        return $eventArgs->isDefaultPrevented();
2002
    }
2003
2004
    /**
2005
     * @param string[] $columnSql
2006
     *
2007
     * @return bool
2008
     */
2009 57358
    protected function onSchemaAlterTableChangeColumn(ColumnDiff $columnDiff, TableDiff $diff, &$columnSql)
2010
    {
2011 57358
        if ($this->_eventManager === null) {
2012 54216
            return false;
2013
        }
2014
2015 57192
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) {
2016 57174
            return false;
2017
        }
2018
2019 53125
        $eventArgs = new SchemaAlterTableChangeColumnEventArgs($columnDiff, $diff, $this);
2020 53125
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableChangeColumn, $eventArgs);
2021
2022 53125
        $columnSql = array_merge($columnSql, $eventArgs->getSql());
2023
2024 53125
        return $eventArgs->isDefaultPrevented();
2025
    }
2026
2027
    /**
2028
     * @param string   $oldColumnName
2029
     * @param string[] $columnSql
2030
     *
2031
     * @return bool
2032
     */
2033 56572
    protected function onSchemaAlterTableRenameColumn($oldColumnName, Column $column, TableDiff $diff, &$columnSql)
2034
    {
2035 56572
        if ($this->_eventManager === null) {
2036 52590
            return false;
2037
        }
2038
2039 56514
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRenameColumn)) {
2040 56496
            return false;
2041
        }
2042
2043 53125
        $eventArgs = new SchemaAlterTableRenameColumnEventArgs($oldColumnName, $column, $diff, $this);
2044 53125
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRenameColumn, $eventArgs);
2045
2046 53125
        $columnSql = array_merge($columnSql, $eventArgs->getSql());
2047
2048 53125
        return $eventArgs->isDefaultPrevented();
2049
    }
2050
2051
    /**
2052
     * @param string[] $sql
2053
     *
2054
     * @return bool
2055
     */
2056 57964
    protected function onSchemaAlterTable(TableDiff $diff, &$sql)
2057
    {
2058 57964
        if ($this->_eventManager === null) {
2059 54385
            return false;
2060
        }
2061
2062 57629
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTable)) {
2063 57611
            return false;
2064
        }
2065
2066 53125
        $eventArgs = new SchemaAlterTableEventArgs($diff, $this);
2067 53125
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTable, $eventArgs);
2068
2069 53125
        $sql = array_merge($sql, $eventArgs->getSql());
2070
2071 53125
        return $eventArgs->isDefaultPrevented();
2072
    }
2073
2074
    /**
2075
     * @return string[]
2076
     */
2077 57823
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
2078
    {
2079 57823
        $tableName = $diff->getName($this)->getQuotedName($this);
2080
2081 57823
        $sql = [];
2082 57823
        if ($this->supportsForeignKeyConstraints()) {
2083 57823
            foreach ($diff->removedForeignKeys as $foreignKey) {
2084 56297
                $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName);
2085
            }
2086
2087 57823
            foreach ($diff->changedForeignKeys as $foreignKey) {
2088 52299
                $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName);
2089
            }
2090
        }
2091
2092 57823
        foreach ($diff->removedIndexes as $index) {
2093 56712
            $sql[] = $this->getDropIndexSQL($index, $tableName);
2094
        }
2095
2096 57823
        foreach ($diff->changedIndexes as $index) {
2097 56357
            $sql[] = $this->getDropIndexSQL($index, $tableName);
2098
        }
2099
2100 57823
        return $sql;
2101
    }
2102
2103
    /**
2104
     * @return string[]
2105
     */
2106 57823
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
2107
    {
2108 57823
        $sql     = [];
2109 57823
        $newName = $diff->getNewName();
2110
2111 57823
        if ($newName !== false) {
2112 53565
            $tableName = $newName->getQuotedName($this);
2113
        } else {
2114 57788
            $tableName = $diff->getName($this)->getQuotedName($this);
2115
        }
2116
2117 57823
        if ($this->supportsForeignKeyConstraints()) {
2118 57823
            foreach ($diff->addedForeignKeys as $foreignKey) {
2119 56390
                $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
2120
            }
2121
2122 57823
            foreach ($diff->changedForeignKeys as $foreignKey) {
2123 52299
                $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
2124
            }
2125
        }
2126
2127 57823
        foreach ($diff->addedIndexes as $index) {
2128 56352
            $sql[] = $this->getCreateIndexSQL($index, $tableName);
2129
        }
2130
2131 57823
        foreach ($diff->changedIndexes as $index) {
2132 56357
            $sql[] = $this->getCreateIndexSQL($index, $tableName);
2133
        }
2134
2135 57823
        foreach ($diff->renamedIndexes as $oldIndexName => $index) {
2136 56468
            $oldIndexName = new Identifier($oldIndexName);
2137 56468
            $sql          = array_merge(
2138 56468
                $sql,
2139 56468
                $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableName)
2140
            );
2141
        }
2142
2143 57823
        return $sql;
2144
    }
2145
2146
    /**
2147
     * Returns the SQL for renaming an index on a table.
2148
     *
2149
     * @param string $oldIndexName The name of the index to rename from.
2150
     * @param Index  $index        The definition of the index to rename to.
2151
     * @param string $tableName    The table to rename the given index on.
2152
     *
2153
     * @return string[] The sequence of SQL statements for renaming the given index.
2154
     */
2155 52233
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
2156
    {
2157
        return [
2158 52233
            $this->getDropIndexSQL($oldIndexName, $tableName),
2159 52233
            $this->getCreateIndexSQL($index, $tableName),
2160
        ];
2161
    }
2162
2163
    /**
2164
     * Common code for alter table statement generation that updates the changed Index and Foreign Key definitions.
2165
     *
2166
     * @deprecated
2167
     *
2168
     * @return string[]
2169
     */
2170
    protected function _getAlterTableIndexForeignKeySQL(TableDiff $diff)
2171
    {
2172
        return array_merge($this->getPreAlterTableIndexForeignKeySQL($diff), $this->getPostAlterTableIndexForeignKeySQL($diff));
2173
    }
2174
2175
    /**
2176
     * Gets declaration of a number of fields in bulk.
2177
     *
2178
     * @param mixed[][] $fields A multidimensional associative array.
2179
     *                          The first dimension determines the field name, while the second
2180
     *                          dimension is keyed with the name of the properties
2181
     *                          of the field being declared as array indexes. Currently, the types
2182
     *                          of supported field properties are as follows:
2183
     *
2184
     *      length
2185
     *          Integer value that determines the maximum length of the text
2186
     *          field. If this argument is missing the field should be
2187
     *          declared to have the longest length allowed by the DBMS.
2188
     *
2189
     *      default
2190
     *          Text value to be used as default for this field.
2191
     *
2192
     *      notnull
2193
     *          Boolean flag that indicates whether this field is constrained
2194
     *          to not be set to null.
2195
     *      charset
2196
     *          Text value with the default CHARACTER SET for this field.
2197
     *      collation
2198
     *          Text value with the default COLLATION for this field.
2199
     *      unique
2200
     *          unique constraint
2201
     *
2202
     * @return string
2203
     */
2204 59902
    public function getColumnDeclarationListSQL(array $fields)
2205
    {
2206 59902
        $queryFields = [];
2207
2208 59902
        foreach ($fields as $fieldName => $field) {
2209 59902
            $queryFields[] = $this->getColumnDeclarationSQL($fieldName, $field);
2210
        }
2211
2212 59902
        return implode(', ', $queryFields);
2213
    }
2214
2215
    /**
2216
     * Obtains DBMS specific SQL code portion needed to declare a generic type
2217
     * field to be used in statements like CREATE TABLE.
2218
     *
2219
     * @param string  $name  The name the field to be declared.
2220
     * @param mixed[] $field An associative array with the name of the properties
2221
     *                       of the field being declared as array indexes. Currently, the types
2222
     *                       of supported field properties are as follows:
2223
     *
2224
     *      length
2225
     *          Integer value that determines the maximum length of the text
2226
     *          field. If this argument is missing the field should be
2227
     *          declared to have the longest length allowed by the DBMS.
2228
     *
2229
     *      default
2230
     *          Text value to be used as default for this field.
2231
     *
2232
     *      notnull
2233
     *          Boolean flag that indicates whether this field is constrained
2234
     *          to not be set to null.
2235
     *      charset
2236
     *          Text value with the default CHARACTER SET for this field.
2237
     *      collation
2238
     *          Text value with the default COLLATION for this field.
2239
     *      unique
2240
     *          unique constraint
2241
     *      check
2242
     *          column check constraint
2243
     *      columnDefinition
2244
     *          a string that defines the complete column
2245
     *
2246
     * @return string DBMS specific SQL code portion that should be used to declare the column.
2247
     */
2248 59440
    public function getColumnDeclarationSQL($name, array $field)
2249
    {
2250 59440
        if (isset($field['columnDefinition'])) {
2251 54883
            $columnDef = $this->getCustomTypeDeclarationSQL($field);
2252
        } else {
2253 59426
            $default = $this->getDefaultValueDeclarationSQL($field);
2254
2255 59426
            $charset = isset($field['charset']) && $field['charset'] ?
2256 59426
                ' ' . $this->getColumnCharsetDeclarationSQL($field['charset']) : '';
2257
2258 59426
            $collation = isset($field['collation']) && $field['collation'] ?
2259 59426
                ' ' . $this->getColumnCollationDeclarationSQL($field['collation']) : '';
2260
2261 59426
            $notnull = isset($field['notnull']) && $field['notnull'] ? ' NOT NULL' : '';
2262
2263 59426
            $unique = isset($field['unique']) && $field['unique'] ?
2264 59426
                ' ' . $this->getUniqueFieldDeclarationSQL() : '';
2265
2266 59426
            $check = isset($field['check']) && $field['check'] ?
2267 59426
                ' ' . $field['check'] : '';
2268
2269 59426
            $typeDecl  = $field['type']->getSQLDeclaration($field, $this);
2270 59426
            $columnDef = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation;
2271
2272 59426
            if ($this->supportsInlineColumnComments() && isset($field['comment']) && $field['comment'] !== '') {
2273 54164
                $columnDef .= ' ' . $this->getInlineColumnCommentSQL($field['comment']);
2274
            }
2275
        }
2276
2277 59440
        return $name . ' ' . $columnDef;
2278
    }
2279
2280
    /**
2281
     * Returns the SQL snippet that declares a floating point column of arbitrary precision.
2282
     *
2283
     * @param mixed[] $columnDef
2284
     *
2285
     * @return string
2286
     */
2287 57153
    public function getDecimalTypeDeclarationSQL(array $columnDef)
2288
    {
2289 57153
        $columnDef['precision'] = ! isset($columnDef['precision']) || empty($columnDef['precision'])
2290 57153
            ? 10 : $columnDef['precision'];
2291 57153
        $columnDef['scale']     = ! isset($columnDef['scale']) || empty($columnDef['scale'])
2292 57153
            ? 0 : $columnDef['scale'];
2293
2294 57153
        return 'NUMERIC(' . $columnDef['precision'] . ', ' . $columnDef['scale'] . ')';
2295
    }
2296
2297
    /**
2298
     * Obtains DBMS specific SQL code portion needed to set a default value
2299
     * declaration to be used in statements like CREATE TABLE.
2300
     *
2301
     * @param mixed[] $field The field definition array.
2302
     *
2303
     * @return string DBMS specific SQL code portion needed to set a default value.
2304
     */
2305 59981
    public function getDefaultValueDeclarationSQL($field)
2306
    {
2307 59981
        if (! isset($field['default'])) {
2308 59560
            return empty($field['notnull']) ? ' DEFAULT NULL' : '';
2309
        }
2310
2311 58109
        $default = $field['default'];
2312
2313 58109
        if (! isset($field['type'])) {
2314 13260
            return " DEFAULT '" . $default . "'";
2315
        }
2316
2317 58097
        $type = $field['type'];
2318
2319 58097
        if ($type instanceof Types\PhpIntegerMappingType) {
2320 57532
            return ' DEFAULT ' . $default;
2321
        }
2322
2323 58068
        if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) {
2324 57955
            return ' DEFAULT ' . $this->getCurrentTimestampSQL();
2325
        }
2326
2327 57807
        if ($type instanceof Types\TimeType && $default === $this->getCurrentTimeSQL()) {
2328 37615
            return ' DEFAULT ' . $this->getCurrentTimeSQL();
2329
        }
2330
2331 57807
        if ($type instanceof Types\DateType && $default === $this->getCurrentDateSQL()) {
2332 56061
            return ' DEFAULT ' . $this->getCurrentDateSQL();
2333
        }
2334
2335 57547
        if ($type instanceof Types\BooleanType) {
2336 57494
            return " DEFAULT '" . $this->convertBooleans($default) . "'";
2337
        }
2338
2339 57521
        return ' DEFAULT ' . $this->quoteStringLiteral($default);
2340
    }
2341
2342
    /**
2343
     * Obtains DBMS specific SQL code portion needed to set a CHECK constraint
2344
     * declaration to be used in statements like CREATE TABLE.
2345
     *
2346
     * @param string[]|mixed[][] $definition The check definition.
2347
     *
2348
     * @return string DBMS specific SQL code portion needed to set a CHECK constraint.
2349
     */
2350 55185
    public function getCheckDeclarationSQL(array $definition)
2351
    {
2352 55185
        $constraints = [];
2353 55185
        foreach ($definition as $field => $def) {
2354 55185
            if (is_string($def)) {
2355
                $constraints[] = 'CHECK (' . $def . ')';
2356
            } else {
2357 55185
                if (isset($def['min'])) {
2358 54331
                    $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')';
2359
                }
2360
2361 55185
                if (isset($def['max'])) {
2362 54331
                    $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')';
2363
                }
2364
            }
2365
        }
2366
2367 55185
        return implode(', ', $constraints);
2368
    }
2369
2370
    /**
2371
     * Obtains DBMS specific SQL code portion needed to set a unique
2372
     * constraint declaration to be used in statements like CREATE TABLE.
2373
     *
2374
     * @param string $name  The name of the unique constraint.
2375
     * @param Index  $index The index definition.
2376
     *
2377
     * @return string DBMS specific SQL code portion needed to set a constraint.
2378
     *
2379
     * @throws InvalidArgumentException
2380
     */
2381 53319
    public function getUniqueConstraintDeclarationSQL($name, Index $index)
2382
    {
2383 53319
        $columns = $index->getColumns();
2384 53319
        $name    = new Identifier($name);
2385
2386 53319
        if (count($columns) === 0) {
2387
            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
2388
        }
2389
2390 53319
        return 'CONSTRAINT ' . $name->getQuotedName($this) . ' UNIQUE ('
2391 53319
            . $this->getIndexFieldDeclarationListSQL($index)
2392 53319
            . ')' . $this->getPartialIndexSQL($index);
2393
    }
2394
2395
    /**
2396
     * Obtains DBMS specific SQL code portion needed to set an index
2397
     * declaration to be used in statements like CREATE TABLE.
2398
     *
2399
     * @param string $name  The name of the index.
2400
     * @param Index  $index The index definition.
2401
     *
2402
     * @return string DBMS specific SQL code portion needed to set an index.
2403
     *
2404
     * @throws InvalidArgumentException
2405
     */
2406 55360
    public function getIndexDeclarationSQL($name, Index $index)
2407
    {
2408 55360
        $columns = $index->getColumns();
2409 55360
        $name    = new Identifier($name);
2410
2411 55360
        if (count($columns) === 0) {
2412
            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
2413
        }
2414
2415 55360
        return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name->getQuotedName($this) . ' ('
2416 55360
            . $this->getIndexFieldDeclarationListSQL($index)
2417 55360
            . ')' . $this->getPartialIndexSQL($index);
2418
    }
2419
2420
    /**
2421
     * Obtains SQL code portion needed to create a custom column,
2422
     * e.g. when a field has the "columnDefinition" keyword.
2423
     * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate.
2424
     *
2425
     * @param mixed[] $columnDef
2426
     *
2427
     * @return string
2428
     */
2429 54887
    public function getCustomTypeDeclarationSQL(array $columnDef)
2430
    {
2431 54887
        return $columnDef['columnDefinition'];
2432
    }
2433
2434
    /**
2435
     * Obtains DBMS specific SQL code portion needed to set an index
2436
     * declaration to be used in statements like CREATE TABLE.
2437
     *
2438
     * @param mixed[]|Index $columnsOrIndex array declaration is deprecated, prefer passing Index to this method
2439
     */
2440 58659
    public function getIndexFieldDeclarationListSQL($columnsOrIndex) : string
2441
    {
2442 58659
        if ($columnsOrIndex instanceof Index) {
2443 58619
            return implode(', ', $columnsOrIndex->getQuotedColumns($this));
2444
        }
2445
2446 26519
        if (! is_array($columnsOrIndex)) {
2447
            throw new InvalidArgumentException('Fields argument should be an Index or array.');
2448
        }
2449
2450 26519
        $ret = [];
2451
2452 26519
        foreach ($columnsOrIndex as $column => $definition) {
2453 26519
            if (is_array($definition)) {
2454
                $ret[] = $column;
2455
            } else {
2456 26519
                $ret[] = $definition;
2457
            }
2458
        }
2459
2460 26519
        return implode(', ', $ret);
2461
    }
2462
2463
    /**
2464
     * Returns the required SQL string that fits between CREATE ... TABLE
2465
     * to create the table as a temporary table.
2466
     *
2467
     * Should be overridden in driver classes to return the correct string for the
2468
     * specific database type.
2469
     *
2470
     * The default is to return the string "TEMPORARY" - this will result in a
2471
     * SQL error for any database that does not support temporary tables, or that
2472
     * requires a different SQL command from "CREATE TEMPORARY TABLE".
2473
     *
2474
     * @return string The string required to be placed between "CREATE" and "TABLE"
2475
     *                to generate a temporary table, if possible.
2476
     */
2477
    public function getTemporaryTableSQL()
2478
    {
2479
        return 'TEMPORARY';
2480
    }
2481
2482
    /**
2483
     * Some vendors require temporary table names to be qualified specially.
2484
     *
2485
     * @param string $tableName
2486
     *
2487
     * @return string
2488
     */
2489 45628
    public function getTemporaryTableName($tableName)
2490
    {
2491 45628
        return $tableName;
2492
    }
2493
2494
    /**
2495
     * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
2496
     * of a field declaration to be used in statements like CREATE TABLE.
2497
     *
2498
     * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
2499
     *                of a field declaration.
2500
     */
2501 58503
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
2502
    {
2503 58503
        $sql  = $this->getForeignKeyBaseDeclarationSQL($foreignKey);
2504 58491
        $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey);
2505
2506 58491
        return $sql;
2507
    }
2508
2509
    /**
2510
     * Returns the FOREIGN KEY query section dealing with non-standard options
2511
     * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
2512
     *
2513
     * @param ForeignKeyConstraint $foreignKey The foreign key definition.
2514
     *
2515
     * @return string
2516
     */
2517 58474
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
2518
    {
2519 58474
        $query = '';
2520 58474
        if ($this->supportsForeignKeyOnUpdate() && $foreignKey->hasOption('onUpdate')) {
2521 26063
            $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate'));
2522
        }
2523
2524 58474
        if ($foreignKey->hasOption('onDelete')) {
2525 56809
            $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
2526
        }
2527
2528 58474
        return $query;
2529
    }
2530
2531
    /**
2532
     * Returns the given referential action in uppercase if valid, otherwise throws an exception.
2533
     *
2534
     * @param string $action The foreign key referential action.
2535
     *
2536
     * @return string
2537
     *
2538
     * @throws InvalidArgumentException If unknown referential action given.
2539
     */
2540 56921
    public function getForeignKeyReferentialActionSQL($action)
2541
    {
2542 56921
        $upper = strtoupper($action);
2543 121
        switch ($upper) {
2544 56921
            case 'CASCADE':
2545 54870
            case 'SET NULL':
2546 54833
            case 'NO ACTION':
2547 54804
            case 'RESTRICT':
2548 54774
            case 'SET DEFAULT':
2549 56904
                return $upper;
2550
2551
            default:
2552 53584
                throw new InvalidArgumentException('Invalid foreign key action: ' . $upper);
2553
        }
2554
    }
2555
2556
    /**
2557
     * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
2558
     * of a field declaration to be used in statements like CREATE TABLE.
2559
     *
2560
     * @return string
2561
     *
2562
     * @throws InvalidArgumentException
2563
     */
2564 58467
    public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey)
2565
    {
2566 58467
        $sql = '';
2567 58467
        if (strlen($foreignKey->getName())) {
2568 58453
            $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' ';
2569
        }
2570
2571 58467
        $sql .= 'FOREIGN KEY (';
2572
2573 58467
        if (count($foreignKey->getLocalColumns()) === 0) {
2574
            throw new InvalidArgumentException("Incomplete definition. 'local' required.");
2575
        }
2576
2577 58467
        if (count($foreignKey->getForeignColumns()) === 0) {
2578
            throw new InvalidArgumentException("Incomplete definition. 'foreign' required.");
2579
        }
2580
2581 58467
        if (strlen($foreignKey->getForeignTableName()) === 0) {
2582
            throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required.");
2583
        }
2584
2585 58467
        return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this))
2586 58467
            . ') REFERENCES '
2587 58467
            . $foreignKey->getQuotedForeignTableName($this) . ' ('
2588 58467
            . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')';
2589
    }
2590
2591
    /**
2592
     * Obtains DBMS specific SQL code portion needed to set the UNIQUE constraint
2593
     * of a field declaration to be used in statements like CREATE TABLE.
2594
     *
2595
     * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint
2596
     *                of a field declaration.
2597
     */
2598
    public function getUniqueFieldDeclarationSQL()
2599
    {
2600
        return 'UNIQUE';
2601
    }
2602
2603
    /**
2604
     * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET
2605
     * of a field declaration to be used in statements like CREATE TABLE.
2606
     *
2607
     * @param string $charset The name of the charset.
2608
     *
2609
     * @return string DBMS specific SQL code portion needed to set the CHARACTER SET
2610
     *                of a field declaration.
2611
     */
2612
    public function getColumnCharsetDeclarationSQL($charset)
2613
    {
2614
        return '';
2615
    }
2616
2617
    /**
2618
     * Obtains DBMS specific SQL code portion needed to set the COLLATION
2619
     * of a field declaration to be used in statements like CREATE TABLE.
2620
     *
2621
     * @param string $collation The name of the collation.
2622
     *
2623
     * @return string DBMS specific SQL code portion needed to set the COLLATION
2624
     *                of a field declaration.
2625
     */
2626 18523
    public function getColumnCollationDeclarationSQL($collation)
2627
    {
2628 18523
        return $this->supportsColumnCollation() ? 'COLLATE ' . $collation : '';
2629
    }
2630
2631
    /**
2632
     * Whether the platform prefers sequences for ID generation.
2633
     * Subclasses should override this method to return TRUE if they prefer sequences.
2634
     *
2635
     * @return bool
2636
     */
2637 5
    public function prefersSequences()
2638
    {
2639 5
        return false;
2640
    }
2641
2642
    /**
2643
     * Whether the platform prefers identity columns (eg. autoincrement) for ID generation.
2644
     * Subclasses should override this method to return TRUE if they prefer identity columns.
2645
     *
2646
     * @return bool
2647
     */
2648 46898
    public function prefersIdentityColumns()
2649
    {
2650 46898
        return false;
2651
    }
2652
2653
    /**
2654
     * Some platforms need the boolean values to be converted.
2655
     *
2656
     * The default conversion in this implementation converts to integers (false => 0, true => 1).
2657
     *
2658
     * Note: if the input is not a boolean the original input might be returned.
2659
     *
2660
     * There are two contexts when converting booleans: Literals and Prepared Statements.
2661
     * This method should handle the literal case
2662
     *
2663
     * @param mixed $item A boolean or an array of them.
2664
     *
2665
     * @return mixed A boolean database value or an array of them.
2666
     */
2667 55789
    public function convertBooleans($item)
2668
    {
2669 55789
        if (is_array($item)) {
2670
            foreach ($item as $k => $value) {
2671
                if (! is_bool($value)) {
2672
                    continue;
2673
                }
2674
2675
                $item[$k] = (int) $value;
2676
            }
2677 55789
        } elseif (is_bool($item)) {
2678 55788
            $item = (int) $item;
2679
        }
2680
2681 55789
        return $item;
2682
    }
2683
2684
    /**
2685
     * Some platforms have boolean literals that needs to be correctly converted
2686
     *
2687
     * The default conversion tries to convert value into bool "(bool)$item"
2688
     *
2689
     * @param mixed $item
2690
     *
2691
     * @return bool|null
2692
     */
2693 55199
    public function convertFromBoolean($item)
2694
    {
2695 55199
        return $item === null ? null: (bool) $item;
2696
    }
2697
2698
    /**
2699
     * This method should handle the prepared statements case. When there is no
2700
     * distinction, it's OK to use the same method.
2701
     *
2702
     * Note: if the input is not a boolean the original input might be returned.
2703
     *
2704
     * @param mixed $item A boolean or an array of them.
2705
     *
2706
     * @return mixed A boolean database value or an array of them.
2707
     */
2708 50652
    public function convertBooleansToDatabaseValue($item)
2709
    {
2710 50652
        return $this->convertBooleans($item);
2711
    }
2712
2713
    /**
2714
     * Returns the SQL specific for the platform to get the current date.
2715
     *
2716
     * @return string
2717
     */
2718 56479
    public function getCurrentDateSQL()
2719
    {
2720 56479
        return 'CURRENT_DATE';
2721
    }
2722
2723
    /**
2724
     * Returns the SQL specific for the platform to get the current time.
2725
     *
2726
     * @return string
2727
     */
2728 30097
    public function getCurrentTimeSQL()
2729
    {
2730 30097
        return 'CURRENT_TIME';
2731
    }
2732
2733
    /**
2734
     * Returns the SQL specific for the platform to get the current timestamp
2735
     *
2736
     * @return string
2737
     */
2738 57646
    public function getCurrentTimestampSQL()
2739
    {
2740 57646
        return 'CURRENT_TIMESTAMP';
2741
    }
2742
2743
    /**
2744
     * Returns the SQL for a given transaction isolation level Connection constant.
2745
     *
2746
     * @param int $level
2747
     *
2748
     * @return string
2749
     *
2750
     * @throws InvalidArgumentException
2751
     */
2752 51945
    protected function _getTransactionIsolationLevelSQL($level)
2753
    {
2754 11
        switch ($level) {
2755 51934
            case TransactionIsolationLevel::READ_UNCOMMITTED:
2756 51945
                return 'READ UNCOMMITTED';
2757
2758 51934
            case TransactionIsolationLevel::READ_COMMITTED:
2759 51945
                return 'READ COMMITTED';
2760
2761 51934
            case TransactionIsolationLevel::REPEATABLE_READ:
2762 51945
                return 'REPEATABLE READ';
2763
2764 51934
            case TransactionIsolationLevel::SERIALIZABLE:
2765 51945
                return 'SERIALIZABLE';
2766
2767
            default:
2768
                throw new InvalidArgumentException('Invalid isolation level:' . $level);
2769
        }
2770
    }
2771
2772
    /**
2773
     * @return string
2774
     *
2775
     * @throws DBALException If not supported on this platform.
2776
     */
2777 2481
    public function getListDatabasesSQL()
2778
    {
2779 2481
        throw DBALException::notSupported(__METHOD__);
2780
    }
2781
2782
    /**
2783
     * Returns the SQL statement for retrieving the namespaces defined in the database.
2784
     *
2785
     * @return string
2786
     *
2787
     * @throws DBALException If not supported on this platform.
2788
     */
2789
    public function getListNamespacesSQL()
2790
    {
2791
        throw DBALException::notSupported(__METHOD__);
2792
    }
2793
2794
    /**
2795
     * @param string $database
2796
     *
2797
     * @return string
2798
     *
2799
     * @throws DBALException If not supported on this platform.
2800
     */
2801
    public function getListSequencesSQL($database)
2802
    {
2803
        throw DBALException::notSupported(__METHOD__);
2804
    }
2805
2806
    /**
2807
     * @param string $table
2808
     *
2809
     * @return string
2810
     *
2811
     * @throws DBALException If not supported on this platform.
2812
     */
2813
    public function getListTableConstraintsSQL($table)
2814
    {
2815
        throw DBALException::notSupported(__METHOD__);
2816
    }
2817
2818
    /**
2819
     * @param string      $table
2820
     * @param string|null $database
2821
     *
2822
     * @return string
2823
     *
2824
     * @throws DBALException If not supported on this platform.
2825
     */
2826
    public function getListTableColumnsSQL($table, $database = null)
2827
    {
2828
        throw DBALException::notSupported(__METHOD__);
2829
    }
2830
2831
    /**
2832
     * @return string
2833
     *
2834
     * @throws DBALException If not supported on this platform.
2835
     */
2836
    public function getListTablesSQL()
2837
    {
2838
        throw DBALException::notSupported(__METHOD__);
2839
    }
2840
2841
    /**
2842
     * @return string
2843
     *
2844
     * @throws DBALException If not supported on this platform.
2845
     */
2846
    public function getListUsersSQL()
2847
    {
2848
        throw DBALException::notSupported(__METHOD__);
2849
    }
2850
2851
    /**
2852
     * Returns the SQL to list all views of a database or user.
2853
     *
2854
     * @param string $database
2855
     *
2856
     * @return string
2857
     *
2858
     * @throws DBALException If not supported on this platform.
2859
     */
2860
    public function getListViewsSQL($database)
2861
    {
2862
        throw DBALException::notSupported(__METHOD__);
2863
    }
2864
2865
    /**
2866
     * Returns the list of indexes for the current database.
2867
     *
2868
     * The current database parameter is optional but will always be passed
2869
     * when using the SchemaManager API and is the database the given table is in.
2870
     *
2871
     * Attention: Some platforms only support currentDatabase when they
2872
     * are connected with that database. Cross-database information schema
2873
     * requests may be impossible.
2874
     *
2875
     * @param string $table
2876
     * @param string $currentDatabase
2877
     *
2878
     * @return string
2879
     *
2880
     * @throws DBALException If not supported on this platform.
2881
     */
2882
    public function getListTableIndexesSQL($table, $currentDatabase = null)
2883
    {
2884
        throw DBALException::notSupported(__METHOD__);
2885
    }
2886
2887
    /**
2888
     * @param string $table
2889
     *
2890
     * @return string
2891
     *
2892
     * @throws DBALException If not supported on this platform.
2893
     */
2894
    public function getListTableForeignKeysSQL($table)
2895
    {
2896
        throw DBALException::notSupported(__METHOD__);
2897
    }
2898
2899
    /**
2900
     * @param string $name
2901
     * @param string $sql
2902
     *
2903
     * @return string
2904
     *
2905
     * @throws DBALException If not supported on this platform.
2906
     */
2907
    public function getCreateViewSQL($name, $sql)
2908
    {
2909
        throw DBALException::notSupported(__METHOD__);
2910
    }
2911
2912
    /**
2913
     * @param string $name
2914
     *
2915
     * @return string
2916
     *
2917
     * @throws DBALException If not supported on this platform.
2918
     */
2919
    public function getDropViewSQL($name)
2920
    {
2921
        throw DBALException::notSupported(__METHOD__);
2922
    }
2923
2924
    /**
2925
     * Returns the SQL snippet to drop an existing sequence.
2926
     *
2927
     * @param Sequence|string $sequence
2928
     *
2929
     * @return string
2930
     *
2931
     * @throws DBALException If not supported on this platform.
2932
     */
2933
    public function getDropSequenceSQL($sequence)
2934
    {
2935
        throw DBALException::notSupported(__METHOD__);
2936
    }
2937
2938
    /**
2939
     * @param string $sequenceName
2940
     *
2941
     * @return string
2942
     *
2943
     * @throws DBALException If not supported on this platform.
2944
     */
2945
    public function getSequenceNextValSQL($sequenceName)
2946
    {
2947
        throw DBALException::notSupported(__METHOD__);
2948
    }
2949
2950
    /**
2951
     * Returns the SQL to create a new database.
2952
     *
2953
     * @param string $database The name of the database that should be created.
2954
     *
2955
     * @return string
2956
     *
2957
     * @throws DBALException If not supported on this platform.
2958
     */
2959 43655
    public function getCreateDatabaseSQL($database)
2960
    {
2961 43655
        throw DBALException::notSupported(__METHOD__);
2962
    }
2963
2964
    /**
2965
     * Returns the SQL to set the transaction isolation level.
2966
     *
2967
     * @param int $level
2968
     *
2969
     * @return string
2970
     *
2971
     * @throws DBALException If not supported on this platform.
2972
     */
2973
    public function getSetTransactionIsolationSQL($level)
2974
    {
2975
        throw DBALException::notSupported(__METHOD__);
2976
    }
2977
2978
    /**
2979
     * Obtains DBMS specific SQL to be used to create datetime fields in
2980
     * statements like CREATE TABLE.
2981
     *
2982
     * @param mixed[] $fieldDeclaration
2983
     *
2984
     * @return string
2985
     *
2986
     * @throws DBALException If not supported on this platform.
2987
     */
2988
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
2989
    {
2990
        throw DBALException::notSupported(__METHOD__);
2991
    }
2992
2993
    /**
2994
     * Obtains DBMS specific SQL to be used to create datetime with timezone offset fields.
2995
     *
2996
     * @param mixed[] $fieldDeclaration
2997
     *
2998
     * @return string
2999
     */
3000 33569
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
3001
    {
3002 33569
        return $this->getDateTimeTypeDeclarationSQL($fieldDeclaration);
3003
    }
3004
3005
    /**
3006
     * Obtains DBMS specific SQL to be used to create date fields in statements
3007
     * like CREATE TABLE.
3008
     *
3009
     * @param mixed[] $fieldDeclaration
3010
     *
3011
     * @return string
3012
     *
3013
     * @throws DBALException If not supported on this platform.
3014
     */
3015
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
0 ignored issues
show
Unused Code introduced by
The parameter $fieldDeclaration is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
3016
    {
3017
        throw DBALException::notSupported(__METHOD__);
3018
    }
3019
3020
    /**
3021
     * Obtains DBMS specific SQL to be used to create time fields in statements
3022
     * like CREATE TABLE.
3023
     *
3024
     * @param mixed[] $fieldDeclaration
3025
     *
3026
     * @return string
3027
     *
3028
     * @throws DBALException If not supported on this platform.
3029
     */
3030
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
0 ignored issues
show
Unused Code introduced by
The parameter $fieldDeclaration is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
3031
    {
3032
        throw DBALException::notSupported(__METHOD__);
3033
    }
3034
3035
    /**
3036
     * @param mixed[] $fieldDeclaration
3037
     *
3038
     * @return string
3039
     */
3040 54280
    public function getFloatDeclarationSQL(array $fieldDeclaration)
3041
    {
3042 54280
        return 'DOUBLE PRECISION';
3043
    }
3044
3045
    /**
3046
     * Gets the default transaction isolation level of the platform.
3047
     *
3048
     * @see TransactionIsolationLevel
3049
     *
3050
     * @return int The default isolation level.
3051
     */
3052
    public function getDefaultTransactionIsolationLevel()
3053
    {
3054
        return TransactionIsolationLevel::READ_COMMITTED;
3055
    }
3056
3057
    /* supports*() methods */
3058
3059
    /**
3060
     * Whether the platform supports sequences.
3061
     *
3062
     * @return bool
3063
     */
3064 36010
    public function supportsSequences()
3065
    {
3066 36010
        return false;
3067
    }
3068
3069
    /**
3070
     * Whether the platform supports identity columns.
3071
     *
3072
     * Identity columns are columns that receive an auto-generated value from the
3073
     * database on insert of a row.
3074
     *
3075
     * @return bool
3076
     */
3077 3
    public function supportsIdentityColumns()
3078
    {
3079 3
        return false;
3080
    }
3081
3082
    /**
3083
     * Whether the platform emulates identity columns through sequences.
3084
     *
3085
     * Some platforms that do not support identity columns natively
3086
     * but support sequences can emulate identity columns by using
3087
     * sequences.
3088
     *
3089
     * @return bool
3090
     */
3091 53930
    public function usesSequenceEmulatedIdentityColumns()
3092
    {
3093 53930
        return false;
3094
    }
3095
3096
    /**
3097
     * Returns the name of the sequence for a particular identity column in a particular table.
3098
     *
3099
     * @see    usesSequenceEmulatedIdentityColumns
3100
     *
3101
     * @param string $tableName  The name of the table to return the sequence name for.
3102
     * @param string $columnName The name of the identity column in the table to return the sequence name for.
3103
     *
3104
     * @return string
3105
     *
3106
     * @throws DBALException If not supported on this platform.
3107
     */
3108 52659
    public function getIdentitySequenceName($tableName, $columnName)
3109
    {
3110 52659
        throw DBALException::notSupported(__METHOD__);
3111
    }
3112
3113
    /**
3114
     * Whether the platform supports indexes.
3115
     *
3116
     * @return bool
3117
     */
3118 4
    public function supportsIndexes()
3119
    {
3120 4
        return true;
3121
    }
3122
3123
    /**
3124
     * Whether the platform supports partial indexes.
3125
     *
3126
     * @return bool
3127
     */
3128 57253
    public function supportsPartialIndexes()
3129
    {
3130 57253
        return false;
3131
    }
3132
3133
    /**
3134
     * Whether the platform supports indexes with column length definitions.
3135
     */
3136 57087
    public function supportsColumnLengthIndexes() : bool
3137
    {
3138 57087
        return false;
3139
    }
3140
3141
    /**
3142
     * Whether the platform supports altering tables.
3143
     *
3144
     * @return bool
3145
     */
3146 56553
    public function supportsAlterTable()
3147
    {
3148 56553
        return true;
3149
    }
3150
3151
    /**
3152
     * Whether the platform supports transactions.
3153
     *
3154
     * @return bool
3155
     */
3156 4
    public function supportsTransactions()
3157
    {
3158 4
        return true;
3159
    }
3160
3161
    /**
3162
     * Whether the platform supports savepoints.
3163
     *
3164
     * @return bool
3165
     */
3166 57914
    public function supportsSavepoints()
3167
    {
3168 57914
        return true;
3169
    }
3170
3171
    /**
3172
     * Whether the platform supports releasing savepoints.
3173
     *
3174
     * @return bool
3175
     */
3176 55037
    public function supportsReleaseSavepoints()
3177
    {
3178 55037
        return $this->supportsSavepoints();
3179
    }
3180
3181
    /**
3182
     * Whether the platform supports primary key constraints.
3183
     *
3184
     * @return bool
3185
     */
3186 4
    public function supportsPrimaryConstraints()
3187
    {
3188 4
        return true;
3189
    }
3190
3191
    /**
3192
     * Whether the platform supports foreign key constraints.
3193
     *
3194
     * @return bool
3195
     */
3196 58709
    public function supportsForeignKeyConstraints()
3197
    {
3198 58709
        return true;
3199
    }
3200
3201
    /**
3202
     * Whether foreign key constraints can be dropped.
3203
     *
3204
     * If false, then getDropForeignKeySQL() throws exception.
3205
     */
3206 57477
    public function supportsCreateDropForeignKeyConstraints() : bool
3207
    {
3208 57477
        return true;
3209
    }
3210
3211
    /**
3212
     * Whether this platform supports onUpdate in foreign key constraints.
3213
     *
3214
     * @return bool
3215
     */
3216 58478
    public function supportsForeignKeyOnUpdate()
3217
    {
3218 58478
        return $this->supportsForeignKeyConstraints();
3219
    }
3220
3221
    /**
3222
     * Whether the platform supports database schemas.
3223
     *
3224
     * @return bool
3225
     */
3226 36016
    public function supportsSchemas()
3227
    {
3228 36016
        return false;
3229
    }
3230
3231
    /**
3232
     * Whether this platform can emulate schemas.
3233
     *
3234
     * Platforms that either support or emulate schemas don't automatically
3235
     * filter a schema for the namespaced elements in {@link
3236
     * AbstractManager#createSchema}.
3237
     *
3238
     * @return bool
3239
     */
3240 4
    public function canEmulateSchemas()
3241
    {
3242 4
        return false;
3243
    }
3244
3245
    /**
3246
     * Returns the default schema name.
3247
     *
3248
     * @return string
3249
     *
3250
     * @throws DBALException If not supported on this platform.
3251
     */
3252
    public function getDefaultSchemaName()
3253
    {
3254
        throw DBALException::notSupported(__METHOD__);
3255
    }
3256
3257
    /**
3258
     * Whether this platform supports create database.
3259
     *
3260
     * Some databases don't allow to create and drop databases at all or only with certain tools.
3261
     *
3262
     * @return bool
3263
     */
3264 54016
    public function supportsCreateDropDatabase()
3265
    {
3266 54016
        return true;
3267
    }
3268
3269
    /**
3270
     * Whether the platform supports getting the affected rows of a recent update/delete type query.
3271
     *
3272
     * @return bool
3273
     */
3274 4
    public function supportsGettingAffectedRows()
3275
    {
3276 4
        return true;
3277
    }
3278
3279
    /**
3280
     * Whether this platform support to add inline column comments as postfix.
3281
     *
3282
     * @return bool
3283
     */
3284 56565
    public function supportsInlineColumnComments()
3285
    {
3286 56565
        return false;
3287
    }
3288
3289
    /**
3290
     * Whether this platform support the proprietary syntax "COMMENT ON asset".
3291
     *
3292
     * @return bool
3293
     */
3294 56981
    public function supportsCommentOnStatement()
3295
    {
3296 56981
        return false;
3297
    }
3298
3299
    /**
3300
     * Does this platform have native guid type.
3301
     *
3302
     * @return bool
3303
     */
3304 57811
    public function hasNativeGuidType()
3305
    {
3306 57811
        return false;
3307
    }
3308
3309
    /**
3310
     * Does this platform have native JSON type.
3311
     *
3312
     * @return bool
3313
     */
3314 57654
    public function hasNativeJsonType()
3315
    {
3316 57654
        return false;
3317
    }
3318
3319
    /**
3320
     * @deprecated
3321
     *
3322
     * @return string
3323
     *
3324
     * @todo Remove in 3.0
3325
     */
3326
    public function getIdentityColumnNullInsertSQL()
3327
    {
3328
        return '';
3329
    }
3330
3331
    /**
3332
     * Whether this platform supports views.
3333
     *
3334
     * @return bool
3335
     */
3336 56517
    public function supportsViews()
3337
    {
3338 56517
        return true;
3339
    }
3340
3341
    /**
3342
     * Does this platform support column collation?
3343
     *
3344
     * @return bool
3345
     */
3346
    public function supportsColumnCollation()
3347
    {
3348
        return false;
3349
    }
3350
3351
    /**
3352
     * Gets the format string, as accepted by the date() function, that describes
3353
     * the format of a stored datetime value of this platform.
3354
     *
3355
     * @return string The format string.
3356
     */
3357 54142
    public function getDateTimeFormatString()
3358
    {
3359 54142
        return 'Y-m-d H:i:s';
3360
    }
3361
3362
    /**
3363
     * Gets the format string, as accepted by the date() function, that describes
3364
     * the format of a stored datetime with timezone value of this platform.
3365
     *
3366
     * @return string The format string.
3367
     */
3368 33832
    public function getDateTimeTzFormatString()
3369
    {
3370 33832
        return 'Y-m-d H:i:s';
3371
    }
3372
3373
    /**
3374
     * Gets the format string, as accepted by the date() function, that describes
3375
     * the format of a stored date value of this platform.
3376
     *
3377
     * @return string The format string.
3378
     */
3379 50223
    public function getDateFormatString()
3380
    {
3381 50223
        return 'Y-m-d';
3382
    }
3383
3384
    /**
3385
     * Gets the format string, as accepted by the date() function, that describes
3386
     * the format of a stored time value of this platform.
3387
     *
3388
     * @return string The format string.
3389
     */
3390 45373
    public function getTimeFormatString()
3391
    {
3392 45373
        return 'H:i:s';
3393
    }
3394
3395
    /**
3396
     * Adds an driver-specific LIMIT clause to the query.
3397
     *
3398
     * @param string   $query
3399
     * @param int|null $limit
3400
     * @param int|null $offset
3401
     *
3402
     * @return string
3403
     *
3404
     * @throws DBALException
3405
     */
3406 58349
    final public function modifyLimitQuery($query, $limit, $offset = null)
3407
    {
3408 58349
        if ($limit !== null) {
3409 58321
            $limit = (int) $limit;
3410
        }
3411
3412 58349
        $offset = (int) $offset;
3413
3414 58349
        if ($offset < 0) {
3415
            throw new DBALException(sprintf(
3416
                'Offset must be a positive integer or zero, %d given',
3417
                $offset
3418
            ));
3419
        }
3420
3421 58349
        if ($offset > 0 && ! $this->supportsLimitOffset()) {
3422
            throw new DBALException(sprintf(
3423
                'Platform %s does not support offset values in limit queries.',
3424
                $this->getName()
3425
            ));
3426
        }
3427
3428 58349
        return $this->doModifyLimitQuery($query, $limit, $offset);
3429
    }
3430
3431
    /**
3432
     * Adds an platform-specific LIMIT clause to the query.
3433
     *
3434
     * @param string   $query
3435
     * @param int|null $limit
3436
     * @param int|null $offset
3437
     *
3438
     * @return string
3439
     */
3440 46807
    protected function doModifyLimitQuery($query, $limit, $offset)
3441
    {
3442 46807
        if ($limit !== null) {
3443 46801
            $query .= ' LIMIT ' . $limit;
3444
        }
3445
3446 46807
        if ($offset > 0) {
3447 20299
            $query .= ' OFFSET ' . $offset;
3448
        }
3449
3450 46807
        return $query;
3451
    }
3452
3453
    /**
3454
     * Whether the database platform support offsets in modify limit clauses.
3455
     *
3456
     * @return bool
3457
     */
3458 57898
    public function supportsLimitOffset()
3459
    {
3460 57898
        return true;
3461
    }
3462
3463
    /**
3464
     * Gets the character casing of a column in an SQL result set of this platform.
3465
     *
3466
     * @param string $column The column name for which to get the correct character casing.
3467
     *
3468
     * @return string The column name in the character casing used in SQL result sets.
3469
     */
3470
    public function getSQLResultCasing($column)
3471
    {
3472
        return $column;
3473
    }
3474
3475
    /**
3476
     * Makes any fixes to a name of a schema element (table, sequence, ...) that are required
3477
     * by restrictions of the platform, like a maximum length.
3478
     *
3479
     * @param string $schemaElementName
3480
     *
3481
     * @return string
3482
     */
3483
    public function fixSchemaElementName($schemaElementName)
3484
    {
3485
        return $schemaElementName;
3486
    }
3487
3488
    /**
3489
     * Maximum length of any given database identifier, like tables or column names.
3490
     *
3491
     * @return int
3492
     */
3493 57643
    public function getMaxIdentifierLength()
3494
    {
3495 57643
        return 63;
3496
    }
3497
3498
    /**
3499
     * Returns the insert SQL for an empty insert statement.
3500
     *
3501
     * @param string $tableName
3502
     * @param string $identifierColumnName
3503
     *
3504
     * @return string
3505
     */
3506 30785
    public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName)
3507
    {
3508 30785
        return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (null)';
3509
    }
3510
3511
    /**
3512
     * Generates a Truncate Table SQL statement for a given table.
3513
     *
3514
     * Cascade is not supported on many platforms but would optionally cascade the truncate by
3515
     * following the foreign keys.
3516
     *
3517
     * @param string $tableName
3518
     * @param bool   $cascade
3519
     *
3520
     * @return string
3521
     */
3522 54522
    public function getTruncateTableSQL($tableName, $cascade = false)
3523
    {
3524 54522
        $tableIdentifier = new Identifier($tableName);
3525
3526 54522
        return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this);
3527
    }
3528
3529
    /**
3530
     * This is for test reasons, many vendors have special requirements for dummy statements.
3531
     *
3532
     * @return string
3533
     */
3534 59277
    public function getDummySelectSQL()
3535
    {
3536 59277
        $expression = func_num_args() > 0 ? func_get_arg(0) : '1';
3537
3538 59277
        return sprintf('SELECT %s', $expression);
3539
    }
3540
3541
    /**
3542
     * Returns the SQL to create a new savepoint.
3543
     *
3544
     * @param string $savepoint
3545
     *
3546
     * @return string
3547
     */
3548 51713
    public function createSavePoint($savepoint)
3549
    {
3550 51713
        return 'SAVEPOINT ' . $savepoint;
3551
    }
3552
3553
    /**
3554
     * Returns the SQL to release a savepoint.
3555
     *
3556
     * @param string $savepoint
3557
     *
3558
     * @return string
3559
     */
3560 51712
    public function releaseSavePoint($savepoint)
3561
    {
3562 51712
        return 'RELEASE SAVEPOINT ' . $savepoint;
3563
    }
3564
3565
    /**
3566
     * Returns the SQL to rollback a savepoint.
3567
     *
3568
     * @param string $savepoint
3569
     *
3570
     * @return string
3571
     */
3572 51713
    public function rollbackSavePoint($savepoint)
3573
    {
3574 51713
        return 'ROLLBACK TO SAVEPOINT ' . $savepoint;
3575
    }
3576
3577
    /**
3578
     * Returns the keyword list instance of this platform.
3579
     *
3580
     * @return KeywordList
3581
     *
3582
     * @throws DBALException If no keyword list is specified.
3583
     */
3584 60621
    final public function getReservedKeywordsList()
3585
    {
3586
        // Check for an existing instantiation of the keywords class.
3587 60621
        if ($this->_keywords) {
3588 60554
            return $this->_keywords;
3589
        }
3590
3591 60452
        $class    = $this->getReservedKeywordsClass();
3592 60452
        $keywords = new $class();
3593 60452
        if (! $keywords instanceof KeywordList) {
3594
            throw DBALException::notSupported(__METHOD__);
3595
        }
3596
3597
        // Store the instance so it doesn't need to be generated on every request.
3598 60452
        $this->_keywords = $keywords;
3599
3600 60452
        return $keywords;
3601
    }
3602
3603
    /**
3604
     * Returns the class name of the reserved keywords list.
3605
     *
3606
     * @return string
3607
     *
3608
     * @throws DBALException If not supported on this platform.
3609
     */
3610
    protected function getReservedKeywordsClass()
3611
    {
3612
        throw DBALException::notSupported(__METHOD__);
3613
    }
3614
3615
    /**
3616
     * Quotes a literal string.
3617
     * This method is NOT meant to fix SQL injections!
3618
     * It is only meant to escape this platform's string literal
3619
     * quote character inside the given literal string.
3620
     *
3621
     * @param string $str The literal string to be quoted.
3622
     *
3623
     * @return string The quoted literal string.
3624
     */
3625 58375
    public function quoteStringLiteral($str)
3626
    {
3627 58375
        $c = $this->getStringLiteralQuoteCharacter();
3628
3629 58375
        return $c . str_replace($c, $c . $c, $str) . $c;
3630
    }
3631
3632
    /**
3633
     * Gets the character used for string literal quoting.
3634
     *
3635
     * @return string
3636
     */
3637 58393
    public function getStringLiteralQuoteCharacter()
3638
    {
3639 58393
        return "'";
3640
    }
3641
3642
    /**
3643
     * Escapes metacharacters in a string intended to be used with a LIKE
3644
     * operator.
3645
     *
3646
     * @param string $inputString a literal, unquoted string
3647
     * @param string $escapeChar  should be reused by the caller in the LIKE
3648
     *                            expression.
3649
     */
3650 58359
    final public function escapeStringForLike(string $inputString, string $escapeChar) : string
3651
    {
3652 58359
        return preg_replace(
3653 58359
            '~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u',
3654 58359
            addcslashes($escapeChar, '\\') . '$1',
3655 58359
            $inputString
3656
        );
3657
    }
3658
3659 58359
    protected function getLikeWildcardCharacters() : string
3660
    {
3661 58359
        return '%_';
3662
    }
3663
}
3664