Completed
Push — master ( 9b88bf...94cec7 )
by Marco
31s queued 15s
created

lib/Doctrine/DBAL/Platforms/AbstractPlatform.php (1 issue)

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 const E_USER_DEPRECATED;
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 count;
37
use function explode;
38
use function func_get_arg;
39
use function func_get_args;
40
use function func_num_args;
41
use function implode;
42
use function in_array;
43
use function is_array;
44
use function is_bool;
45
use function is_int;
46
use function is_string;
47
use function preg_quote;
48
use function preg_replace;
49
use function sprintf;
50
use function str_replace;
51
use function strlen;
52
use function strpos;
53
use function strtolower;
54
use function strtoupper;
55
use function trigger_error;
56
57
/**
58
 * Base class for all DatabasePlatforms. The DatabasePlatforms are the central
59
 * point of abstraction of platform-specific behaviors, features and SQL dialects.
60
 * They are a passive source of information.
61
 *
62
 * @todo Remove any unnecessary methods.
63
 */
64
abstract class AbstractPlatform
65
{
66
    public const CREATE_INDEXES = 1;
67
68
    public const CREATE_FOREIGNKEYS = 2;
69
70
    /**
71
     * @deprecated Use DateIntervalUnit::INTERVAL_UNIT_SECOND.
72
     */
73
    public const DATE_INTERVAL_UNIT_SECOND = DateIntervalUnit::SECOND;
74
75
    /**
76
     * @deprecated Use DateIntervalUnit::MINUTE.
77
     */
78
    public const DATE_INTERVAL_UNIT_MINUTE = DateIntervalUnit::MINUTE;
79
80
    /**
81
     * @deprecated Use DateIntervalUnit::HOUR.
82
     */
83
    public const DATE_INTERVAL_UNIT_HOUR = DateIntervalUnit::HOUR;
84
85
    /**
86
     * @deprecated Use DateIntervalUnit::DAY.
87
     */
88
    public const DATE_INTERVAL_UNIT_DAY = DateIntervalUnit::DAY;
89
90
    /**
91
     * @deprecated Use DateIntervalUnit::WEEK.
92
     */
93
    public const DATE_INTERVAL_UNIT_WEEK = DateIntervalUnit::WEEK;
94
95
    /**
96
     * @deprecated Use DateIntervalUnit::MONTH.
97
     */
98
    public const DATE_INTERVAL_UNIT_MONTH = DateIntervalUnit::MONTH;
99
100
    /**
101
     * @deprecated Use DateIntervalUnit::QUARTER.
102
     */
103
    public const DATE_INTERVAL_UNIT_QUARTER = DateIntervalUnit::QUARTER;
104
105
    /**
106
     * @deprecated Use DateIntervalUnit::QUARTER.
107
     */
108
    public const DATE_INTERVAL_UNIT_YEAR = DateIntervalUnit::YEAR;
109
110
    /**
111
     * @deprecated Use TrimMode::UNSPECIFIED.
112
     */
113
    public const TRIM_UNSPECIFIED = TrimMode::UNSPECIFIED;
114
115
    /**
116
     * @deprecated Use TrimMode::LEADING.
117
     */
118
    public const TRIM_LEADING = TrimMode::LEADING;
119
120
    /**
121
     * @deprecated Use TrimMode::TRAILING.
122
     */
123
    public const TRIM_TRAILING = TrimMode::TRAILING;
124
125
    /**
126
     * @deprecated Use TrimMode::BOTH.
127
     */
128
    public const TRIM_BOTH = TrimMode::BOTH;
129
130
    /** @var string[]|null */
131
    protected $doctrineTypeMapping = null;
132
133
    /**
134
     * Contains a list of all columns that should generate parseable column comments for type-detection
135
     * in reverse engineering scenarios.
136
     *
137
     * @var string[]|null
138
     */
139
    protected $doctrineTypeComments = null;
140
141
    /** @var EventManager */
142
    protected $_eventManager;
143
144
    /**
145
     * Holds the KeywordList instance for the current platform.
146
     *
147
     * @var KeywordList|null
148
     */
149
    protected $_keywords;
150
151 95461
    public function __construct()
152
    {
153 95461
    }
154
155
    /**
156
     * Sets the EventManager used by the Platform.
157
     */
158 2446
    public function setEventManager(EventManager $eventManager)
159
    {
160 2446
        $this->_eventManager = $eventManager;
161 2446
    }
162
163
    /**
164
     * Gets the EventManager used by the Platform.
165
     *
166
     * @return EventManager
167
     */
168 1617
    public function getEventManager()
169
    {
170 1617
        return $this->_eventManager;
171
    }
172
173
    /**
174
     * Returns the SQL snippet that declares a boolean column.
175
     *
176
     * @param mixed[] $columnDef
177
     *
178
     * @return string
179
     */
180
    abstract public function getBooleanTypeDeclarationSQL(array $columnDef);
181
182
    /**
183
     * Returns the SQL snippet that declares a 4 byte integer column.
184
     *
185
     * @param mixed[] $columnDef
186
     *
187
     * @return string
188
     */
189
    abstract public function getIntegerTypeDeclarationSQL(array $columnDef);
190
191
    /**
192
     * Returns the SQL snippet that declares an 8 byte integer column.
193
     *
194
     * @param mixed[] $columnDef
195
     *
196
     * @return string
197
     */
198
    abstract public function getBigIntTypeDeclarationSQL(array $columnDef);
199
200
    /**
201
     * Returns the SQL snippet that declares a 2 byte integer column.
202
     *
203
     * @param mixed[] $columnDef
204
     *
205
     * @return string
206
     */
207
    abstract public function getSmallIntTypeDeclarationSQL(array $columnDef);
208
209
    /**
210
     * Returns the SQL snippet that declares common properties of an integer column.
211
     *
212
     * @param mixed[] $columnDef
213
     *
214
     * @return string
215
     */
216
    abstract protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef);
217
218
    /**
219
     * Lazy load Doctrine Type Mappings.
220
     *
221
     * @return void
222
     */
223
    abstract protected function initializeDoctrineTypeMappings();
224
225
    /**
226
     * Initializes Doctrine Type Mappings with the platform defaults
227
     * and with all additional type mappings.
228
     *
229
     * @return void
230
     */
231 2687
    private function initializeAllDoctrineTypeMappings()
232
    {
233 2687
        $this->initializeDoctrineTypeMappings();
234
235 2687
        foreach (Type::getTypesMap() as $typeName => $className) {
236 2687
            foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) {
237 1584
                $this->doctrineTypeMapping[$dbType] = $typeName;
238
            }
239
        }
240 2687
    }
241
242
    /**
243
     * Returns the SQL snippet used to declare a VARCHAR column type.
244
     *
245
     * @param mixed[] $field
246
     *
247
     * @return string
248
     */
249 9474
    public function getVarcharTypeDeclarationSQL(array $field)
250
    {
251 9474
        if (! isset($field['length'])) {
252 2234
            $field['length'] = $this->getVarcharDefaultLength();
253
        }
254
255 9474
        $fixed = $field['fixed'] ?? false;
256
257 9474
        $maxLength = $fixed
258 1337
            ? $this->getCharMaxLength()
259 9474
            : $this->getVarcharMaxLength();
260
261 9474
        if ($field['length'] > $maxLength) {
262
            return $this->getClobTypeDeclarationSQL($field);
263
        }
264
265 9474
        return $this->getVarcharTypeDeclarationSQLSnippet($field['length'], $fixed);
266
    }
267
268
    /**
269
     * Returns the SQL snippet used to declare a BINARY/VARBINARY column type.
270
     *
271
     * @param mixed[] $field The column definition.
272
     *
273
     * @return string
274
     */
275 862
    public function getBinaryTypeDeclarationSQL(array $field)
276
    {
277 862
        if (! isset($field['length'])) {
278 512
            $field['length'] = $this->getBinaryDefaultLength();
279
        }
280
281 862
        $fixed = $field['fixed'] ?? false;
282
283 862
        $maxLength = $this->getBinaryMaxLength();
284
285 862
        if ($field['length'] > $maxLength) {
286 494
            if ($maxLength > 0) {
287 324
                @trigger_error(sprintf(
288 324
                    'Binary field length %d is greater than supported by the platform (%d). Reduce the field length or use a BLOB field instead.',
289 324
                    $field['length'],
290 324
                    $maxLength
291 324
                ), E_USER_DEPRECATED);
292
            }
293
294 494
            return $this->getBlobTypeDeclarationSQL($field);
295
        }
296
297 530
        return $this->getBinaryTypeDeclarationSQLSnippet($field['length'], $fixed);
298
    }
299
300
    /**
301
     * Returns the SQL snippet to declare a GUID/UUID field.
302
     *
303
     * By default this maps directly to a CHAR(36) and only maps to more
304
     * special datatypes when the underlying databases support this datatype.
305
     *
306
     * @param mixed[] $field
307
     *
308
     * @return string
309
     */
310 176
    public function getGuidTypeDeclarationSQL(array $field)
311
    {
312 176
        $field['length'] = 36;
313 176
        $field['fixed']  = true;
314
315 176
        return $this->getVarcharTypeDeclarationSQL($field);
316
    }
317
318
    /**
319
     * Returns the SQL snippet to declare a JSON field.
320
     *
321
     * By default this maps directly to a CLOB and only maps to more
322
     * special datatypes when the underlying databases support this datatype.
323
     *
324
     * @param mixed[] $field
325
     *
326
     * @return string
327
     */
328 560
    public function getJsonTypeDeclarationSQL(array $field)
329
    {
330 560
        return $this->getClobTypeDeclarationSQL($field);
331
    }
332
333
    /**
334
     * @param int  $length
335
     * @param bool $fixed
336
     *
337
     * @return string
338
     *
339
     * @throws DBALException If not supported on this platform.
340
     */
341
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
342
    {
343
        throw DBALException::notSupported('VARCHARs not supported by Platform.');
344
    }
345
346
    /**
347
     * Returns the SQL snippet used to declare a BINARY/VARBINARY column type.
348
     *
349
     * @param int  $length The length of the column.
350
     * @param bool $fixed  Whether the column length is fixed.
351
     *
352
     * @return string
353
     *
354
     * @throws DBALException If not supported on this platform.
355
     */
356
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
357
    {
358
        throw DBALException::notSupported('BINARY/VARBINARY column types are not supported by this platform.');
359
    }
360
361
    /**
362
     * Returns the SQL snippet used to declare a CLOB column type.
363
     *
364
     * @param mixed[] $field
365
     *
366
     * @return string
367
     */
368
    abstract public function getClobTypeDeclarationSQL(array $field);
369
370
    /**
371
     * Returns the SQL Snippet used to declare a BLOB column type.
372
     *
373
     * @param mixed[] $field
374
     *
375
     * @return string
376
     */
377
    abstract public function getBlobTypeDeclarationSQL(array $field);
378
379
    /**
380
     * Gets the name of the platform.
381
     *
382
     * @return string
383
     */
384
    abstract public function getName();
385
386
    /**
387
     * Registers a doctrine type to be used in conjunction with a column type of this platform.
388
     *
389
     * @param string $dbType
390
     * @param string $doctrineType
391
     *
392
     * @throws DBALException If the type is not found.
393
     */
394 1492
    public function registerDoctrineTypeMapping($dbType, $doctrineType)
395
    {
396 1492
        if ($this->doctrineTypeMapping === null) {
397 1485
            $this->initializeAllDoctrineTypeMappings();
398
        }
399
400 1492
        if (! Types\Type::hasType($doctrineType)) {
401 486
            throw DBALException::typeNotFound($doctrineType);
402
        }
403
404 1006
        $dbType                             = strtolower($dbType);
405 1006
        $this->doctrineTypeMapping[$dbType] = $doctrineType;
406
407 1006
        $doctrineType = Type::getType($doctrineType);
408
409 1006
        if (! $doctrineType->requiresSQLCommentHint($this)) {
410 520
            return;
411
        }
412
413 486
        $this->markDoctrineTypeCommented($doctrineType);
414 486
    }
415
416
    /**
417
     * Gets the Doctrine type that is mapped for the given database column type.
418
     *
419
     * @param string $dbType
420
     *
421
     * @return string
422
     *
423
     * @throws DBALException
424
     */
425 3069
    public function getDoctrineTypeMapping($dbType)
426
    {
427 3069
        if ($this->doctrineTypeMapping === null) {
428 581
            $this->initializeAllDoctrineTypeMappings();
429
        }
430
431 3069
        $dbType = strtolower($dbType);
432
433 3069
        if (! isset($this->doctrineTypeMapping[$dbType])) {
434 486
            throw new DBALException('Unknown database type ' . $dbType . ' requested, ' . static::class . ' may not support it.');
435
        }
436
437 2583
        return $this->doctrineTypeMapping[$dbType];
438
    }
439
440
    /**
441
     * Checks if a database type is currently supported by this platform.
442
     *
443
     * @param string $dbType
444
     *
445
     * @return bool
446
     */
447 676
    public function hasDoctrineTypeMappingFor($dbType)
448
    {
449 676
        if ($this->doctrineTypeMapping === null) {
450 621
            $this->initializeAllDoctrineTypeMappings();
451
        }
452
453 676
        $dbType = strtolower($dbType);
454
455 676
        return isset($this->doctrineTypeMapping[$dbType]);
456
    }
457
458
    /**
459
     * Initializes the Doctrine Type comments instance variable for in_array() checks.
460
     *
461
     * @return void
462
     */
463 24404
    protected function initializeCommentedDoctrineTypes()
464
    {
465 24404
        $this->doctrineTypeComments = [];
466
467 24404
        foreach (Type::getTypesMap() as $typeName => $className) {
468 24404
            $type = Type::getType($typeName);
469
470 24404
            if (! $type->requiresSQLCommentHint($this)) {
471 24404
                continue;
472
            }
473
474 24404
            $this->doctrineTypeComments[] = $typeName;
475
        }
476 24404
    }
477
478
    /**
479
     * Is it necessary for the platform to add a parsable type comment to allow reverse engineering the given type?
480
     *
481
     * @return bool
482
     */
483 28445
    public function isCommentedDoctrineType(Type $doctrineType)
484
    {
485 28445
        if ($this->doctrineTypeComments === null) {
486 23918
            $this->initializeCommentedDoctrineTypes();
487
        }
488
489 28445
        return in_array($doctrineType->getName(), $this->doctrineTypeComments);
490
    }
491
492
    /**
493
     * Marks this type as to be commented in ALTER TABLE and CREATE TABLE statements.
494
     *
495
     * @param string|Type $doctrineType
496
     *
497
     * @return void
498
     */
499 486
    public function markDoctrineTypeCommented($doctrineType)
500
    {
501 486
        if ($this->doctrineTypeComments === null) {
502 486
            $this->initializeCommentedDoctrineTypes();
503
        }
504
505 486
        $this->doctrineTypeComments[] = $doctrineType instanceof Type ? $doctrineType->getName() : $doctrineType;
506 486
    }
507
508
    /**
509
     * Gets the comment to append to a column comment that helps parsing this type in reverse engineering.
510
     *
511
     * @return string
512
     */
513 1290
    public function getDoctrineTypeComment(Type $doctrineType)
514
    {
515 1290
        return '(DC2Type:' . $doctrineType->getName() . ')';
516
    }
517
518
    /**
519
     * Gets the comment of a passed column modified by potential doctrine type comment hints.
520
     *
521
     * @return string|null
522
     */
523 15838
    protected function getColumnComment(Column $column)
524
    {
525 15838
        $comment = $column->getComment();
526
527 15838
        if ($this->isCommentedDoctrineType($column->getType())) {
528 1290
            $comment .= $this->getDoctrineTypeComment($column->getType());
529
        }
530
531 15838
        return $comment;
532
    }
533
534
    /**
535
     * Gets the character used for identifier quoting.
536
     *
537
     * @return string
538
     */
539 8261
    public function getIdentifierQuoteCharacter()
540
    {
541 8261
        return '"';
542
    }
543
544
    /**
545
     * Gets the string portion that starts an SQL comment.
546
     *
547
     * @return string
548
     */
549
    public function getSqlCommentStartString()
550
    {
551
        return '--';
552
    }
553
554
    /**
555
     * Gets the string portion that ends an SQL comment.
556
     *
557
     * @return string
558
     */
559
    public function getSqlCommentEndString()
560
    {
561
        return "\n";
562
    }
563
564
    /**
565
     * Gets the maximum length of a char field.
566
     */
567 1222
    public function getCharMaxLength() : int
568
    {
569 1222
        return $this->getVarcharMaxLength();
570
    }
571
572
    /**
573
     * Gets the maximum length of a varchar field.
574
     *
575
     * @return int
576
     */
577 3515
    public function getVarcharMaxLength()
578
    {
579 3515
        return 4000;
580
    }
581
582
    /**
583
     * Gets the default length of a varchar field.
584
     *
585
     * @return int
586
     */
587 1910
    public function getVarcharDefaultLength()
588
    {
589 1910
        return 255;
590
    }
591
592
    /**
593
     * Gets the maximum length of a binary field.
594
     *
595
     * @return int
596
     */
597
    public function getBinaryMaxLength()
598
    {
599
        return 4000;
600
    }
601
602
    /**
603
     * Gets the default length of a binary field.
604
     *
605
     * @return int
606
     */
607 396
    public function getBinaryDefaultLength()
608
    {
609 396
        return 255;
610
    }
611
612
    /**
613
     * Gets all SQL wildcard characters of the platform.
614
     *
615
     * @return string[]
616
     */
617
    public function getWildcards()
618
    {
619
        return ['%', '_'];
620
    }
621
622
    /**
623
     * Returns the regular expression operator.
624
     *
625
     * @return string
626
     *
627
     * @throws DBALException If not supported on this platform.
628
     */
629 135
    public function getRegexpExpression()
630
    {
631 135
        throw DBALException::notSupported(__METHOD__);
632
    }
633
634
    /**
635
     * Returns the global unique identifier expression.
636
     *
637
     * @deprecated Use application-generated UUIDs instead
638
     *
639
     * @return string
640
     *
641
     * @throws DBALException If not supported on this platform.
642
     */
643
    public function getGuidExpression()
644
    {
645
        throw DBALException::notSupported(__METHOD__);
646
    }
647
648
    /**
649
     * Returns the SQL snippet to get the average value of a column.
650
     *
651
     * @param string $column The column to use.
652
     *
653
     * @return string Generated SQL including an AVG aggregate function.
654
     */
655
    public function getAvgExpression($column)
656
    {
657
        return 'AVG(' . $column . ')';
658
    }
659
660
    /**
661
     * Returns the SQL snippet to get the number of rows (without a NULL value) of a column.
662
     *
663
     * If a '*' is used instead of a column the number of selected rows is returned.
664
     *
665
     * @param string|int $column The column to use.
666
     *
667
     * @return string Generated SQL including a COUNT aggregate function.
668
     */
669
    public function getCountExpression($column)
670
    {
671
        return 'COUNT(' . $column . ')';
672
    }
673
674
    /**
675
     * Returns the SQL snippet to get the highest value of a column.
676
     *
677
     * @param string $column The column to use.
678
     *
679
     * @return string Generated SQL including a MAX aggregate function.
680
     */
681
    public function getMaxExpression($column)
682
    {
683
        return 'MAX(' . $column . ')';
684
    }
685
686
    /**
687
     * Returns the SQL snippet to get the lowest value of a column.
688
     *
689
     * @param string $column The column to use.
690
     *
691
     * @return string Generated SQL including a MIN aggregate function.
692
     */
693
    public function getMinExpression($column)
694
    {
695
        return 'MIN(' . $column . ')';
696
    }
697
698
    /**
699
     * Returns the SQL snippet to get the total sum of a column.
700
     *
701
     * @param string $column The column to use.
702
     *
703
     * @return string Generated SQL including a SUM aggregate function.
704
     */
705
    public function getSumExpression($column)
706
    {
707
        return 'SUM(' . $column . ')';
708
    }
709
710
    // scalar functions
711
712
    /**
713
     * Returns the SQL snippet to get the md5 sum of a field.
714
     *
715
     * Note: Not SQL92, but common functionality.
716
     *
717
     * @param string $column
718
     *
719
     * @return string
720
     */
721
    public function getMd5Expression($column)
722
    {
723
        return 'MD5(' . $column . ')';
724
    }
725
726
    /**
727
     * Returns the SQL snippet to get the length of a text field.
728
     *
729
     * @param string $column
730
     *
731
     * @return string
732
     */
733
    public function getLengthExpression($column)
734
    {
735
        return 'LENGTH(' . $column . ')';
736
    }
737
738
    /**
739
     * Returns the SQL snippet to get the squared value of a column.
740
     *
741
     * @param string $column The column to use.
742
     *
743
     * @return string Generated SQL including an SQRT aggregate function.
744
     */
745
    public function getSqrtExpression($column)
746
    {
747
        return 'SQRT(' . $column . ')';
748
    }
749
750
    /**
751
     * Returns the SQL snippet to round a numeric field to the number of decimals specified.
752
     *
753
     * @param string $column
754
     * @param int    $decimals
755
     *
756
     * @return string
757
     */
758
    public function getRoundExpression($column, $decimals = 0)
759
    {
760
        return 'ROUND(' . $column . ', ' . $decimals . ')';
761
    }
762
763
    /**
764
     * Returns the SQL snippet to get the remainder of the division operation $expression1 / $expression2.
765
     *
766
     * @param string $expression1
767
     * @param string $expression2
768
     *
769
     * @return string
770
     */
771
    public function getModExpression($expression1, $expression2)
772
    {
773
        return 'MOD(' . $expression1 . ', ' . $expression2 . ')';
774
    }
775
776
    /**
777
     * Returns the SQL snippet to trim a string.
778
     *
779
     * @param string      $str  The expression to apply the trim to.
780
     * @param int         $mode The position of the trim (leading/trailing/both).
781
     * @param string|bool $char The char to trim, has to be quoted already. Defaults to space.
782
     *
783
     * @return string
784
     */
785 864
    public function getTrimExpression($str, $mode = TrimMode::UNSPECIFIED, $char = false)
786
    {
787 864
        $expression = '';
788
789 864
        switch ($mode) {
790
            case TrimMode::LEADING:
791 216
                $expression = 'LEADING ';
792 216
                break;
793
794
            case TrimMode::TRAILING:
795 216
                $expression = 'TRAILING ';
796 216
                break;
797
798
            case TrimMode::BOTH:
799 216
                $expression = 'BOTH ';
800 216
                break;
801
        }
802
803 864
        if ($char !== false) {
804 672
            $expression .= $char . ' ';
805
        }
806
807 864
        if ($mode || $char !== false) {
808 816
            $expression .= 'FROM ';
809
        }
810
811 864
        return 'TRIM(' . $expression . $str . ')';
812
    }
813
814
    /**
815
     * Returns the SQL snippet to trim trailing space characters from the expression.
816
     *
817
     * @param string $str Literal string or column name.
818
     *
819
     * @return string
820
     */
821 108
    public function getRtrimExpression($str)
822
    {
823 108
        return 'RTRIM(' . $str . ')';
824
    }
825
826
    /**
827
     * Returns the SQL snippet to trim leading space characters from the expression.
828
     *
829
     * @param string $str Literal string or column name.
830
     *
831
     * @return string
832
     */
833 108
    public function getLtrimExpression($str)
834
    {
835 108
        return 'LTRIM(' . $str . ')';
836
    }
837
838
    /**
839
     * Returns the SQL snippet to change all characters from the expression to uppercase,
840
     * according to the current character set mapping.
841
     *
842
     * @param string $str Literal string or column name.
843
     *
844
     * @return string
845
     */
846
    public function getUpperExpression($str)
847
    {
848
        return 'UPPER(' . $str . ')';
849
    }
850
851
    /**
852
     * Returns the SQL snippet to change all characters from the expression to lowercase,
853
     * according to the current character set mapping.
854
     *
855
     * @param string $str Literal string or column name.
856
     *
857
     * @return string
858
     */
859
    public function getLowerExpression($str)
860
    {
861
        return 'LOWER(' . $str . ')';
862
    }
863
864
    /**
865
     * Returns the SQL snippet to get the position of the first occurrence of substring $substr in string $str.
866
     *
867
     * @param string   $str      Literal string.
868
     * @param string   $substr   Literal string to find.
869
     * @param int|bool $startPos Position to start at, beginning of string by default.
870
     *
871
     * @return string
872
     *
873
     * @throws DBALException If not supported on this platform.
874
     */
875
    public function getLocateExpression($str, $substr, $startPos = false)
876
    {
877
        throw DBALException::notSupported(__METHOD__);
878
    }
879
880
    /**
881
     * Returns the SQL snippet to get the current system date.
882
     *
883
     * @return string
884
     */
885
    public function getNowExpression()
886
    {
887
        return 'NOW()';
888
    }
889
890
    /**
891
     * Returns a SQL snippet to get a substring inside an SQL statement.
892
     *
893
     * Note: Not SQL92, but common functionality.
894
     *
895
     * SQLite only supports the 2 parameter variant of this function.
896
     *
897
     * @param string   $value  An sql string literal or column name/alias.
898
     * @param int      $from   Where to start the substring portion.
899
     * @param int|null $length The substring portion length.
900
     *
901
     * @return string
902
     */
903
    public function getSubstringExpression($value, $from, $length = null)
904
    {
905
        if ($length === null) {
906
            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
907
        }
908
909
        return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')';
910
    }
911
912
    /**
913
     * Returns a SQL snippet to concatenate the given expressions.
914
     *
915
     * Accepts an arbitrary number of string parameters. Each parameter must contain an expression.
916
     *
917
     * @return string
918
     */
919 162
    public function getConcatExpression()
920
    {
921 162
        return implode(' || ', func_get_args());
922
    }
923
924
    /**
925
     * Returns the SQL for a logical not.
926
     *
927
     * Example:
928
     * <code>
929
     * $q = new Doctrine_Query();
930
     * $e = $q->expr;
931
     * $q->select('*')->from('table')
932
     *   ->where($e->eq('id', $e->not('null'));
933
     * </code>
934
     *
935
     * @param string $expression
936
     *
937
     * @return string The logical expression.
938
     */
939
    public function getNotExpression($expression)
940
    {
941
        return 'NOT(' . $expression . ')';
942
    }
943
944
    /**
945
     * Returns the SQL that checks if an expression is null.
946
     *
947
     * @param string $expression The expression that should be compared to null.
948
     *
949
     * @return string The logical expression.
950
     */
951 108
    public function getIsNullExpression($expression)
952
    {
953 108
        return $expression . ' IS NULL';
954
    }
955
956
    /**
957
     * Returns the SQL that checks if an expression is not null.
958
     *
959
     * @param string $expression The expression that should be compared to null.
960
     *
961
     * @return string The logical expression.
962
     */
963
    public function getIsNotNullExpression($expression)
964
    {
965
        return $expression . ' IS NOT NULL';
966
    }
967
968
    /**
969
     * Returns the SQL that checks if an expression evaluates to a value between two values.
970
     *
971
     * The parameter $expression is checked if it is between $value1 and $value2.
972
     *
973
     * Note: There is a slight difference in the way BETWEEN works on some databases.
974
     * http://www.w3schools.com/sql/sql_between.asp. If you want complete database
975
     * independence you should avoid using between().
976
     *
977
     * @param string $expression The value to compare to.
978
     * @param string $value1     The lower value to compare with.
979
     * @param string $value2     The higher value to compare with.
980
     *
981
     * @return string The logical expression.
982
     */
983
    public function getBetweenExpression($expression, $value1, $value2)
984
    {
985
        return $expression . ' BETWEEN ' . $value1 . ' AND ' . $value2;
986
    }
987
988
    /**
989
     * Returns the SQL to get the arccosine of a value.
990
     *
991
     * @param string $value
992
     *
993
     * @return string
994
     */
995
    public function getAcosExpression($value)
996
    {
997
        return 'ACOS(' . $value . ')';
998
    }
999
1000
    /**
1001
     * Returns the SQL to get the sine of a value.
1002
     *
1003
     * @param string $value
1004
     *
1005
     * @return string
1006
     */
1007
    public function getSinExpression($value)
1008
    {
1009
        return 'SIN(' . $value . ')';
1010
    }
1011
1012
    /**
1013
     * Returns the SQL to get the PI value.
1014
     *
1015
     * @return string
1016
     */
1017
    public function getPiExpression()
1018
    {
1019
        return 'PI()';
1020
    }
1021
1022
    /**
1023
     * Returns the SQL to get the cosine of a value.
1024
     *
1025
     * @param string $value
1026
     *
1027
     * @return string
1028
     */
1029
    public function getCosExpression($value)
1030
    {
1031
        return 'COS(' . $value . ')';
1032
    }
1033
1034
    /**
1035
     * Returns the SQL to calculate the difference in days between the two passed dates.
1036
     *
1037
     * Computes diff = date1 - date2.
1038
     *
1039
     * @param string $date1
1040
     * @param string $date2
1041
     *
1042
     * @return string
1043
     *
1044
     * @throws DBALException If not supported on this platform.
1045
     */
1046
    public function getDateDiffExpression($date1, $date2)
1047
    {
1048
        throw DBALException::notSupported(__METHOD__);
1049
    }
1050
1051
    /**
1052
     * Returns the SQL to add the number of given seconds to a date.
1053
     *
1054
     * @param string $date
1055
     * @param int    $seconds
1056
     *
1057
     * @return string
1058
     *
1059
     * @throws DBALException If not supported on this platform.
1060
     */
1061 162
    public function getDateAddSecondsExpression($date, $seconds)
1062
    {
1063 162
        return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, DateIntervalUnit::SECOND);
1064
    }
1065
1066
    /**
1067
     * Returns the SQL to subtract the number of given seconds from a date.
1068
     *
1069
     * @param string $date
1070
     * @param int    $seconds
1071
     *
1072
     * @return string
1073
     *
1074
     * @throws DBALException If not supported on this platform.
1075
     */
1076 162
    public function getDateSubSecondsExpression($date, $seconds)
1077
    {
1078 162
        return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, DateIntervalUnit::SECOND);
1079
    }
1080
1081
    /**
1082
     * Returns the SQL to add the number of given minutes to a date.
1083
     *
1084
     * @param string $date
1085
     * @param int    $minutes
1086
     *
1087
     * @return string
1088
     *
1089
     * @throws DBALException If not supported on this platform.
1090
     */
1091 162
    public function getDateAddMinutesExpression($date, $minutes)
1092
    {
1093 162
        return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, DateIntervalUnit::MINUTE);
1094
    }
1095
1096
    /**
1097
     * Returns the SQL to subtract the number of given minutes from a date.
1098
     *
1099
     * @param string $date
1100
     * @param int    $minutes
1101
     *
1102
     * @return string
1103
     *
1104
     * @throws DBALException If not supported on this platform.
1105
     */
1106 162
    public function getDateSubMinutesExpression($date, $minutes)
1107
    {
1108 162
        return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, DateIntervalUnit::MINUTE);
1109
    }
1110
1111
    /**
1112
     * Returns the SQL to add the number of given hours to a date.
1113
     *
1114
     * @param string $date
1115
     * @param int    $hours
1116
     *
1117
     * @return string
1118
     *
1119
     * @throws DBALException If not supported on this platform.
1120
     */
1121 162
    public function getDateAddHourExpression($date, $hours)
1122
    {
1123 162
        return $this->getDateArithmeticIntervalExpression($date, '+', $hours, DateIntervalUnit::HOUR);
1124
    }
1125
1126
    /**
1127
     * Returns the SQL to subtract the number of given hours to a date.
1128
     *
1129
     * @param string $date
1130
     * @param int    $hours
1131
     *
1132
     * @return string
1133
     *
1134
     * @throws DBALException If not supported on this platform.
1135
     */
1136 162
    public function getDateSubHourExpression($date, $hours)
1137
    {
1138 162
        return $this->getDateArithmeticIntervalExpression($date, '-', $hours, DateIntervalUnit::HOUR);
1139
    }
1140
1141
    /**
1142
     * Returns the SQL to add the number of given days to a date.
1143
     *
1144
     * @param string $date
1145
     * @param int    $days
1146
     *
1147
     * @return string
1148
     *
1149
     * @throws DBALException If not supported on this platform.
1150
     */
1151 216
    public function getDateAddDaysExpression($date, $days)
1152
    {
1153 216
        return $this->getDateArithmeticIntervalExpression($date, '+', $days, DateIntervalUnit::DAY);
1154
    }
1155
1156
    /**
1157
     * Returns the SQL to subtract the number of given days to a date.
1158
     *
1159
     * @param string $date
1160
     * @param int    $days
1161
     *
1162
     * @return string
1163
     *
1164
     * @throws DBALException If not supported on this platform.
1165
     */
1166 163
    public function getDateSubDaysExpression($date, $days)
1167
    {
1168 163
        return $this->getDateArithmeticIntervalExpression($date, '-', $days, DateIntervalUnit::DAY);
1169
    }
1170
1171
    /**
1172
     * Returns the SQL to add the number of given weeks to a date.
1173
     *
1174
     * @param string $date
1175
     * @param int    $weeks
1176
     *
1177
     * @return string
1178
     *
1179
     * @throws DBALException If not supported on this platform.
1180
     */
1181 162
    public function getDateAddWeeksExpression($date, $weeks)
1182
    {
1183 162
        return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, DateIntervalUnit::WEEK);
1184
    }
1185
1186
    /**
1187
     * Returns the SQL to subtract the number of given weeks from a date.
1188
     *
1189
     * @param string $date
1190
     * @param int    $weeks
1191
     *
1192
     * @return string
1193
     *
1194
     * @throws DBALException If not supported on this platform.
1195
     */
1196 162
    public function getDateSubWeeksExpression($date, $weeks)
1197
    {
1198 162
        return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, DateIntervalUnit::WEEK);
1199
    }
1200
1201
    /**
1202
     * Returns the SQL to add the number of given months to a date.
1203
     *
1204
     * @param string $date
1205
     * @param int    $months
1206
     *
1207
     * @return string
1208
     *
1209
     * @throws DBALException If not supported on this platform.
1210
     */
1211 162
    public function getDateAddMonthExpression($date, $months)
1212
    {
1213 162
        return $this->getDateArithmeticIntervalExpression($date, '+', $months, DateIntervalUnit::MONTH);
1214
    }
1215
1216
    /**
1217
     * Returns the SQL to subtract the number of given months to a date.
1218
     *
1219
     * @param string $date
1220
     * @param int    $months
1221
     *
1222
     * @return string
1223
     *
1224
     * @throws DBALException If not supported on this platform.
1225
     */
1226 162
    public function getDateSubMonthExpression($date, $months)
1227
    {
1228 162
        return $this->getDateArithmeticIntervalExpression($date, '-', $months, DateIntervalUnit::MONTH);
1229
    }
1230
1231
    /**
1232
     * Returns the SQL to add the number of given quarters to a date.
1233
     *
1234
     * @param string $date
1235
     * @param int    $quarters
1236
     *
1237
     * @return string
1238
     *
1239
     * @throws DBALException If not supported on this platform.
1240
     */
1241 162
    public function getDateAddQuartersExpression($date, $quarters)
1242
    {
1243 162
        return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, DateIntervalUnit::QUARTER);
1244
    }
1245
1246
    /**
1247
     * Returns the SQL to subtract the number of given quarters from a date.
1248
     *
1249
     * @param string $date
1250
     * @param int    $quarters
1251
     *
1252
     * @return string
1253
     *
1254
     * @throws DBALException If not supported on this platform.
1255
     */
1256 162
    public function getDateSubQuartersExpression($date, $quarters)
1257
    {
1258 162
        return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, DateIntervalUnit::QUARTER);
1259
    }
1260
1261
    /**
1262
     * Returns the SQL to add the number of given years to a date.
1263
     *
1264
     * @param string $date
1265
     * @param int    $years
1266
     *
1267
     * @return string
1268
     *
1269
     * @throws DBALException If not supported on this platform.
1270
     */
1271 162
    public function getDateAddYearsExpression($date, $years)
1272
    {
1273 162
        return $this->getDateArithmeticIntervalExpression($date, '+', $years, DateIntervalUnit::YEAR);
1274
    }
1275
1276
    /**
1277
     * Returns the SQL to subtract the number of given years from a date.
1278
     *
1279
     * @param string $date
1280
     * @param int    $years
1281
     *
1282
     * @return string
1283
     *
1284
     * @throws DBALException If not supported on this platform.
1285
     */
1286 162
    public function getDateSubYearsExpression($date, $years)
1287
    {
1288 162
        return $this->getDateArithmeticIntervalExpression($date, '-', $years, DateIntervalUnit::YEAR);
1289
    }
1290
1291
    /**
1292
     * Returns the SQL for a date arithmetic expression.
1293
     *
1294
     * @param string $date     The column or literal representing a date to perform the arithmetic operation on.
1295
     * @param string $operator The arithmetic operator (+ or -).
1296
     * @param int    $interval The interval that shall be calculated into the date.
1297
     * @param string $unit     The unit of the interval that shall be calculated into the date.
1298
     *                         One of the DATE_INTERVAL_UNIT_* constants.
1299
     *
1300
     * @return string
1301
     *
1302
     * @throws DBALException If not supported on this platform.
1303
     */
1304
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
1305
    {
1306
        throw DBALException::notSupported(__METHOD__);
1307
    }
1308
1309
    /**
1310
     * Returns the SQL bit AND comparison expression.
1311
     *
1312
     * @param string $value1
1313
     * @param string $value2
1314
     *
1315
     * @return string
1316
     */
1317 456
    public function getBitAndComparisonExpression($value1, $value2)
1318
    {
1319 456
        return '(' . $value1 . ' & ' . $value2 . ')';
1320
    }
1321
1322
    /**
1323
     * Returns the SQL bit OR comparison expression.
1324
     *
1325
     * @param string $value1
1326
     * @param string $value2
1327
     *
1328
     * @return string
1329
     */
1330 456
    public function getBitOrComparisonExpression($value1, $value2)
1331
    {
1332 456
        return '(' . $value1 . ' | ' . $value2 . ')';
1333
    }
1334
1335
    /**
1336
     * Returns the FOR UPDATE expression.
1337
     *
1338
     * @return string
1339
     */
1340 46
    public function getForUpdateSQL()
1341
    {
1342 46
        return 'FOR UPDATE';
1343
    }
1344
1345
    /**
1346
     * Honors that some SQL vendors such as MsSql use table hints for locking instead of the ANSI SQL FOR UPDATE specification.
1347
     *
1348
     * @param string   $fromClause The FROM clause to append the hint for the given lock mode to.
1349
     * @param int|null $lockMode   One of the Doctrine\DBAL\LockMode::* constants. If null is given, nothing will
1350
     *                             be appended to the FROM clause.
1351
     *
1352
     * @return string
1353
     */
1354 48
    public function appendLockHint($fromClause, $lockMode)
1355
    {
1356 48
        return $fromClause;
1357
    }
1358
1359
    /**
1360
     * Returns the SQL snippet to append to any SELECT statement which locks rows in shared read lock.
1361
     *
1362
     * This defaults to the ANSI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database
1363
     * vendors allow to lighten this constraint up to be a real read lock.
1364
     *
1365
     * @return string
1366
     */
1367
    public function getReadLockSQL()
1368
    {
1369
        return $this->getForUpdateSQL();
1370
    }
1371
1372
    /**
1373
     * Returns the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows.
1374
     *
1375
     * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ANSI SQL standard.
1376
     *
1377
     * @return string
1378
     */
1379 52
    public function getWriteLockSQL()
1380
    {
1381 52
        return $this->getForUpdateSQL();
1382
    }
1383
1384
    /**
1385
     * Returns the SQL snippet to drop an existing database.
1386
     *
1387
     * @param string $database The name of the database that should be dropped.
1388
     *
1389
     * @return string
1390
     */
1391 149
    public function getDropDatabaseSQL($database)
1392
    {
1393 149
        return 'DROP DATABASE ' . $database;
1394
    }
1395
1396
    /**
1397
     * Returns the SQL snippet to drop an existing table.
1398
     *
1399
     * @param Table|string $table
1400
     *
1401
     * @return string
1402
     *
1403
     * @throws InvalidArgumentException
1404
     */
1405 4435
    public function getDropTableSQL($table)
1406
    {
1407 4435
        $tableArg = $table;
1408
1409 4435
        if ($table instanceof Table) {
1410 391
            $table = $table->getQuotedName($this);
1411
        }
1412
1413 4435
        if (! is_string($table)) {
1414
            throw new InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
1415
        }
1416
1417 4435
        if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaDropTable)) {
1418 486
            $eventArgs = new SchemaDropTableEventArgs($tableArg, $this);
1419 486
            $this->_eventManager->dispatchEvent(Events::onSchemaDropTable, $eventArgs);
1420
1421 486
            if ($eventArgs->isDefaultPrevented()) {
1422
                return $eventArgs->getSql();
1423
            }
1424
        }
1425
1426 4435
        return 'DROP TABLE ' . $table;
1427
    }
1428
1429
    /**
1430
     * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction.
1431
     *
1432
     * @param Table|string $table
1433
     *
1434
     * @return string
1435
     */
1436 22
    public function getDropTemporaryTableSQL($table)
1437
    {
1438 22
        return $this->getDropTableSQL($table);
1439
    }
1440
1441
    /**
1442
     * Returns the SQL to drop an index from a table.
1443
     *
1444
     * @param Index|string $index
1445
     * @param Table|string $table
1446
     *
1447
     * @return string
1448
     *
1449
     * @throws InvalidArgumentException
1450
     */
1451 248
    public function getDropIndexSQL($index, $table = null)
1452
    {
1453 248
        if ($index instanceof Index) {
1454 237
            $index = $index->getQuotedName($this);
1455 11
        } elseif (! is_string($index)) {
1456
            throw new InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
1457
        }
1458
1459 248
        return 'DROP INDEX ' . $index;
1460
    }
1461
1462
    /**
1463
     * Returns the SQL to drop a constraint.
1464
     *
1465
     * @param Constraint|string $constraint
1466
     * @param Table|string      $table
1467
     *
1468
     * @return string
1469
     */
1470 1168
    public function getDropConstraintSQL($constraint, $table)
1471
    {
1472 1168
        if (! $constraint instanceof Constraint) {
1473 891
            $constraint = new Identifier($constraint);
1474
        }
1475
1476 1168
        if (! $table instanceof Table) {
1477 1168
            $table = new Identifier($table);
1478
        }
1479
1480 1168
        $constraint = $constraint->getQuotedName($this);
1481 1168
        $table      = $table->getQuotedName($this);
1482
1483 1168
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint;
1484
    }
1485
1486
    /**
1487
     * Returns the SQL to drop a foreign key.
1488
     *
1489
     * @param ForeignKeyConstraint|string $foreignKey
1490
     * @param Table|string                $table
1491
     *
1492
     * @return string
1493
     */
1494 592
    public function getDropForeignKeySQL($foreignKey, $table)
1495
    {
1496 592
        if (! $foreignKey instanceof ForeignKeyConstraint) {
1497 216
            $foreignKey = new Identifier($foreignKey);
1498
        }
1499
1500 592
        if (! $table instanceof Table) {
1501 592
            $table = new Identifier($table);
1502
        }
1503
1504 592
        $foreignKey = $foreignKey->getQuotedName($this);
1505 592
        $table      = $table->getQuotedName($this);
1506
1507 592
        return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey;
1508
    }
1509
1510
    /**
1511
     * Returns the SQL statement(s) to create a table with the specified name, columns and constraints
1512
     * on this platform.
1513
     *
1514
     * @param int $createFlags
1515
     *
1516
     * @return string[] The sequence of SQL statements.
1517
     *
1518
     * @throws DBALException
1519
     * @throws InvalidArgumentException
1520
     */
1521 11963
    public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES)
1522
    {
1523 11963
        if (! is_int($createFlags)) {
1524
            throw new InvalidArgumentException('Second argument of AbstractPlatform::getCreateTableSQL() has to be integer.');
1525
        }
1526
1527 11963
        if (count($table->getColumns()) === 0) {
1528 486
            throw DBALException::noColumnsSpecifiedForTable($table->getName());
1529
        }
1530
1531 11477
        $tableName                    = $table->getQuotedName($this);
1532 11477
        $options                      = $table->getOptions();
1533 11477
        $options['uniqueConstraints'] = [];
1534 11477
        $options['indexes']           = [];
1535 11477
        $options['primary']           = [];
1536
1537 11477
        if (($createFlags&self::CREATE_INDEXES) > 0) {
1538 10856
            foreach ($table->getIndexes() as $index) {
1539
                /** @var $index Index */
1540 7326
                if ($index->isPrimary()) {
1541 5495
                    $options['primary']       = $index->getQuotedColumns($this);
1542 5495
                    $options['primary_index'] = $index;
1543
                } else {
1544 2761
                    $options['indexes'][$index->getQuotedName($this)] = $index;
1545
                }
1546
            }
1547
        }
1548
1549 11477
        $columnSql = [];
1550 11477
        $columns   = [];
1551
1552 11477
        foreach ($table->getColumns() as $column) {
1553 11477
            if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn)) {
1554 486
                $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this);
1555 486
                $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs);
1556
1557 486
                $columnSql = array_merge($columnSql, $eventArgs->getSql());
1558
1559 486
                if ($eventArgs->isDefaultPrevented()) {
1560
                    continue;
1561
                }
1562
            }
1563
1564 11477
            $columnData            = $column->toArray();
1565 11477
            $columnData['name']    = $column->getQuotedName($this);
1566 11477
            $columnData['version'] = $column->hasPlatformOption('version') ? $column->getPlatformOption('version') : false;
1567 11477
            $columnData['comment'] = $this->getColumnComment($column);
1568
1569 11477
            if ($columnData['type'] instanceof Types\StringType && $columnData['length'] === null) {
1570 4259
                $columnData['length'] = 255;
1571
            }
1572
1573 11477
            if (in_array($column->getName(), $options['primary'])) {
1574 4955
                $columnData['primary'] = true;
1575
            }
1576
1577 11477
            $columns[$columnData['name']] = $columnData;
1578
        }
1579
1580 11477
        if (($createFlags&self::CREATE_FOREIGNKEYS) > 0) {
1581 5447
            $options['foreignKeys'] = [];
1582 5447
            foreach ($table->getForeignKeys() as $fkConstraint) {
1583 1039
                $options['foreignKeys'][] = $fkConstraint;
1584
            }
1585
        }
1586
1587 11477
        if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) {
1588 486
            $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this);
1589 486
            $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs);
1590
1591 486
            if ($eventArgs->isDefaultPrevented()) {
1592
                return array_merge($eventArgs->getSql(), $columnSql);
1593
            }
1594
        }
1595
1596 11477
        $sql = $this->_getCreateTableSQL($tableName, $columns, $options);
1597 11477
        if ($this->supportsCommentOnStatement()) {
1598 5353
            foreach ($table->getColumns() as $column) {
1599 5353
                $comment = $this->getColumnComment($column);
1600
1601 5353
                if ($comment === null || $comment === '') {
1602 4996
                    continue;
1603
                }
1604
1605 905
                $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment);
1606
            }
1607
        }
1608
1609 11477
        return array_merge($sql, $columnSql);
1610
    }
1611
1612
    /**
1613
     * @param string      $tableName
1614
     * @param string      $columnName
1615
     * @param string|null $comment
1616
     *
1617
     * @return string
1618
     */
1619 1014
    public function getCommentOnColumnSQL($tableName, $columnName, $comment)
1620
    {
1621 1014
        $tableName  = new Identifier($tableName);
1622 1014
        $columnName = new Identifier($columnName);
1623 1014
1624
        return sprintf(
1625 1014
            'COMMENT ON COLUMN %s.%s IS %s',
1626 1014
            $tableName->getQuotedName($this),
1627 1014
            $columnName->getQuotedName($this),
1628 1014
            $this->quoteStringLiteral((string) $comment)
1629 1014
        );
1630
    }
1631
1632
    /**
1633
     * Returns the SQL to create inline comment on a column.
1634
     *
1635
     * @param string $comment
1636
     *
1637
     * @return string
1638
     *
1639
     * @throws DBALException If not supported on this platform.
1640
     */
1641
    public function getInlineColumnCommentSQL($comment)
1642 1575
    {
1643
        if (! $this->supportsInlineColumnComments()) {
1644 1575
            throw DBALException::notSupported(__METHOD__);
1645 378
        }
1646
1647
        return 'COMMENT ' . $this->quoteStringLiteral($comment);
1648 1197
    }
1649
1650
    /**
1651
     * Returns the SQL used to create a table.
1652
     *
1653
     * @param string    $tableName
1654
     * @param mixed[][] $columns
1655
     * @param mixed[]   $options
1656
     *
1657
     * @return string[]
1658
     */
1659
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
1660 1017
    {
1661
        $columnListSql = $this->getColumnDeclarationListSQL($columns);
1662 1017
1663
        if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
1664 1017
            foreach ($options['uniqueConstraints'] as $name => $definition) {
1665
                $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
1666
            }
1667
        }
1668
1669
        if (isset($options['primary']) && ! empty($options['primary'])) {
1670 1017
            $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')';
1671 531
        }
1672
1673
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
1674 1017
            foreach ($options['indexes'] as $index => $definition) {
1675
                $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition);
1676
            }
1677
        }
1678
1679
        $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;
1680 1017
1681
        $check = $this->getCheckDeclarationSQL($columns);
1682 1017
        if (! empty($check)) {
1683 1017
            $query .= ', ' . $check;
1684 27
        }
1685
        $query .= ')';
1686 1017
1687
        $sql[] = $query;
1688 1017
1689
        if (isset($options['foreignKeys'])) {
1690 1017
            foreach ((array) $options['foreignKeys'] as $definition) {
1691 457
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
1692 103
            }
1693
        }
1694
1695
        return $sql;
1696 1017
    }
1697
1698
    /**
1699
     * @return string
1700
     */
1701
    public function getCreateTemporaryTableSnippetSQL()
1702 44
    {
1703
        return 'CREATE TEMPORARY TABLE';
1704 44
    }
1705
1706
    /**
1707
     * Returns the SQL to create a sequence on this platform.
1708
     *
1709
     * @return string
1710
     *
1711
     * @throws DBALException If not supported on this platform.
1712
     */
1713
    public function getCreateSequenceSQL(Sequence $sequence)
1714
    {
1715
        throw DBALException::notSupported(__METHOD__);
1716
    }
1717
1718
    /**
1719
     * Returns the SQL to change a sequence on this platform.
1720
     *
1721
     * @return string
1722
     *
1723
     * @throws DBALException If not supported on this platform.
1724
     */
1725
    public function getAlterSequenceSQL(Sequence $sequence)
1726
    {
1727
        throw DBALException::notSupported(__METHOD__);
1728
    }
1729
1730
    /**
1731
     * Returns the SQL to create a constraint on a table on this platform.
1732
     *
1733
     * @param Table|string $table
1734
     *
1735
     * @return string
1736
     *
1737
     * @throws InvalidArgumentException
1738
     */
1739
    public function getCreateConstraintSQL(Constraint $constraint, $table)
1740 476
    {
1741
        if ($table instanceof Table) {
1742 476
            $table = $table->getQuotedName($this);
1743
        }
1744
1745
        $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this);
1746 476
1747
        $columnList = '(' . implode(', ', $constraint->getQuotedColumns($this)) . ')';
1748 476
1749
        $referencesClause = '';
1750 476
        if ($constraint instanceof Index) {
1751 476
            if ($constraint->isPrimary()) {
1752 476
                $query .= ' PRIMARY KEY';
1753 476
            } elseif ($constraint->isUnique()) {
1754 351
                $query .= ' UNIQUE';
1755 351
            } else {
1756
                throw new InvalidArgumentException(
1757
                    'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().'
1758 476
                );
1759
            }
1760
        } elseif ($constraint instanceof ForeignKeyConstraint) {
1761 351
            $query .= ' FOREIGN KEY';
1762 351
1763
            $referencesClause = ' REFERENCES ' . $constraint->getQuotedForeignTableName($this) .
1764 351
                ' (' . implode(', ', $constraint->getQuotedForeignColumns($this)) . ')';
1765 351
        }
1766
        $query .= ' ' . $columnList . $referencesClause;
1767 476
1768
        return $query;
1769 476
    }
1770
1771
    /**
1772
     * Returns the SQL to create an index on a table on this platform.
1773
     *
1774
     * @param Table|string $table The name of the table on which the index is to be created.
1775
     *
1776
     * @return string
1777
     *
1778
     * @throws InvalidArgumentException
1779
     */
1780
    public function getCreateIndexSQL(Index $index, $table)
1781 4289
    {
1782
        if ($table instanceof Table) {
1783 4289
            $table = $table->getQuotedName($this);
1784 27
        }
1785
        $name    = $index->getQuotedName($this);
1786 4289
        $columns = $index->getColumns();
1787 4289
1788
        if (count($columns) === 0) {
1789 4289
            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
1790
        }
1791
1792
        if ($index->isPrimary()) {
1793 4289
            return $this->getCreatePrimaryKeySQL($index, $table);
1794 567
        }
1795
1796
        $query  = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table;
1797 3776
        $query .= ' (' . $this->getIndexFieldDeclarationListSQL($index) . ')' . $this->getPartialIndexSQL($index);
1798 3776
1799
        return $query;
1800 3776
    }
1801
1802
    /**
1803
     * Adds condition for partial index.
1804
     *
1805
     * @return string
1806
     */
1807
    protected function getPartialIndexSQL(Index $index)
1808 5259
    {
1809
        if ($this->supportsPartialIndexes() && $index->hasOption('where')) {
1810 5259
            return ' WHERE ' . $index->getOption('where');
1811 142
        }
1812
1813
        return '';
1814 5117
    }
1815
1816
    /**
1817
     * Adds additional flags for index generation.
1818
     *
1819
     * @return string
1820
     */
1821
    protected function getCreateIndexSQLFlags(Index $index)
1822 1843
    {
1823
        return $index->isUnique() ? 'UNIQUE ' : '';
1824 1843
    }
1825
1826
    /**
1827
     * Returns the SQL to create an unnamed primary key constraint.
1828
     *
1829
     * @param Table|string $table
1830
     *
1831
     * @return string
1832
     */
1833
    public function getCreatePrimaryKeySQL(Index $index, $table)
1834 459
    {
1835
        return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index) . ')';
1836 459
    }
1837
1838
    /**
1839
     * Returns the SQL to create a named schema.
1840
     *
1841
     * @param string $schemaName
1842
     *
1843
     * @return string
1844
     *
1845
     * @throws DBALException If not supported on this platform.
1846
     */
1847
    public function getCreateSchemaSQL($schemaName)
0 ignored issues
show
The parameter $schemaName is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

1847
    public function getCreateSchemaSQL(/** @scrutinizer ignore-unused */ $schemaName)

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

Loading history...
1848 270
    {
1849
        throw DBALException::notSupported(__METHOD__);
1850 270
    }
1851
1852
    /**
1853
     * Quotes a string so that it can be safely used as a table or column name,
1854
     * even if it is a reserved word of the platform. This also detects identifier
1855
     * chains separated by dot and quotes them independently.
1856
     *
1857
     * NOTE: Just because you CAN use quoted identifiers doesn't mean
1858
     * you SHOULD use them. In general, they end up causing way more
1859
     * problems than they solve.
1860
     *
1861
     * @param string $str The identifier name to be quoted.
1862
     *
1863
     * @return string The quoted identifier string.
1864
     */
1865
    public function quoteIdentifier($str)
1866 10903
    {
1867
        if (strpos($str, '.') !== false) {
1868 10903
            $parts = array_map([$this, 'quoteSingleIdentifier'], explode('.', $str));
1869 516
1870
            return implode('.', $parts);
1871 516
        }
1872
1873
        return $this->quoteSingleIdentifier($str);
1874 10903
    }
1875
1876
    /**
1877
     * Quotes a single identifier (no dot chain separation).
1878
     *
1879
     * @param string $str The identifier name to be quoted.
1880
     *
1881
     * @return string The quoted identifier string.
1882
     */
1883
    public function quoteSingleIdentifier($str)
1884 10125
    {
1885
        $c = $this->getIdentifierQuoteCharacter();
1886 10125
1887
        return $c . str_replace($c, $c . $c, $str) . $c;
1888 10125
    }
1889
1890
    /**
1891
     * Returns the SQL to create a new foreign key.
1892
     *
1893
     * @param ForeignKeyConstraint $foreignKey The foreign key constraint.
1894
     * @param Table|string         $table      The name of the table on which the foreign key is to be created.
1895
     *
1896
     * @return string
1897
     */
1898
    public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table)
1899 2412
    {
1900
        if ($table instanceof Table) {
1901 2412
            $table = $table->getQuotedName($this);
1902 27
        }
1903
1904
        return 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey);
1905 2412
    }
1906
1907
    /**
1908
     * Gets the SQL statements for altering an existing table.
1909
     *
1910
     * This method returns an array of SQL statements, since some platforms need several statements.
1911
     *
1912
     * @return string[]
1913
     *
1914
     * @throws DBALException If not supported on this platform.
1915
     */
1916
    public function getAlterTableSQL(TableDiff $diff)
1917
    {
1918
        throw DBALException::notSupported(__METHOD__);
1919
    }
1920
1921
    /**
1922
     * @param mixed[] $columnSql
1923
     *
1924
     * @return bool
1925
     */
1926
    protected function onSchemaAlterTableAddColumn(Column $column, TableDiff $diff, &$columnSql)
1927 2691
    {
1928
        if ($this->_eventManager === null) {
1929 2691
            return false;
1930 2160
        }
1931
1932
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableAddColumn)) {
1933 531
            return false;
1934 45
        }
1935
1936
        $eventArgs = new SchemaAlterTableAddColumnEventArgs($column, $diff, $this);
1937 486
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableAddColumn, $eventArgs);
1938 486
1939
        $columnSql = array_merge($columnSql, $eventArgs->getSql());
1940 486
1941
        return $eventArgs->isDefaultPrevented();
1942 486
    }
1943
1944
    /**
1945
     * @param string[] $columnSql
1946
     *
1947
     * @return bool
1948
     */
1949
    protected function onSchemaAlterTableRemoveColumn(Column $column, TableDiff $diff, &$columnSql)
1950 2058
    {
1951
        if ($this->_eventManager === null) {
1952 2058
            return false;
1953 1539
        }
1954
1955
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRemoveColumn)) {
1956 519
            return false;
1957 33
        }
1958
1959
        $eventArgs = new SchemaAlterTableRemoveColumnEventArgs($column, $diff, $this);
1960 486
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRemoveColumn, $eventArgs);
1961 486
1962
        $columnSql = array_merge($columnSql, $eventArgs->getSql());
1963 486
1964
        return $eventArgs->isDefaultPrevented();
1965 486
    }
1966
1967
    /**
1968
     * @param string[] $columnSql
1969
     *
1970
     * @return bool
1971
     */
1972
    protected function onSchemaAlterTableChangeColumn(ColumnDiff $columnDiff, TableDiff $diff, &$columnSql)
1973 5364
    {
1974
        if ($this->_eventManager === null) {
1975 5364
            return false;
1976 4482
        }
1977
1978
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) {
1979 882
            return false;
1980 396
        }
1981
1982
        $eventArgs = new SchemaAlterTableChangeColumnEventArgs($columnDiff, $diff, $this);
1983 486
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableChangeColumn, $eventArgs);
1984 486
1985
        $columnSql = array_merge($columnSql, $eventArgs->getSql());
1986 486
1987
        return $eventArgs->isDefaultPrevented();
1988 486
    }
1989
1990
    /**
1991
     * @param string   $oldColumnName
1992
     * @param string[] $columnSql
1993
     *
1994
     * @return bool
1995
     */
1996
    protected function onSchemaAlterTableRenameColumn($oldColumnName, Column $column, TableDiff $diff, &$columnSql)
1997 2082
    {
1998
        if ($this->_eventManager === null) {
1999 2082
            return false;
2000 1566
        }
2001
2002
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRenameColumn)) {
2003 516
            return false;
2004 30
        }
2005
2006
        $eventArgs = new SchemaAlterTableRenameColumnEventArgs($oldColumnName, $column, $diff, $this);
2007 486
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRenameColumn, $eventArgs);
2008 486
2009
        $columnSql = array_merge($columnSql, $eventArgs->getSql());
2010 486
2011
        return $eventArgs->isDefaultPrevented();
2012 486
    }
2013
2014
    /**
2015
     * @param string[] $sql
2016
     *
2017
     * @return bool
2018
     */
2019
    protected function onSchemaAlterTable(TableDiff $diff, &$sql)
2020 10072
    {
2021
        if ($this->_eventManager === null) {
2022 10072
            return false;
2023 9045
        }
2024
2025
        if (! $this->_eventManager->hasListeners(Events::onSchemaAlterTable)) {
2026 1027
            return false;
2027 541
        }
2028
2029
        $eventArgs = new SchemaAlterTableEventArgs($diff, $this);
2030 486
        $this->_eventManager->dispatchEvent(Events::onSchemaAlterTable, $eventArgs);
2031 486
2032
        $sql = array_merge($sql, $eventArgs->getSql());
2033 486
2034
        return $eventArgs->isDefaultPrevented();
2035 486
    }
2036
2037
    /**
2038
     * @return string[]
2039
     */
2040
    protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff)
2041 9702
    {
2042
        $tableName = $diff->getName($this)->getQuotedName($this);
2043 9702
2044
        $sql = [];
2045 9702
        if ($this->supportsForeignKeyConstraints()) {
2046 9702
            foreach ($diff->removedForeignKeys as $foreignKey) {
2047 9702
                $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName);
2048 701
            }
2049
            foreach ($diff->changedForeignKeys as $foreignKey) {
2050 9702
                $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName);
2051 540
            }
2052
        }
2053
2054
        foreach ($diff->removedIndexes as $index) {
2055 9702
            $sql[] = $this->getDropIndexSQL($index, $tableName);
2056 351
        }
2057
        foreach ($diff->changedIndexes as $index) {
2058 9702
            $sql[] = $this->getDropIndexSQL($index, $tableName);
2059 484
        }
2060
2061
        return $sql;
2062 9702
    }
2063
2064
    /**
2065
     * @return string[]
2066
     */
2067
    protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff)
2068 9702
    {
2069
        $tableName = $diff->newName !== false
2070 9702
            ? $diff->getNewName()->getQuotedName($this)
2071 922
            : $diff->getName($this)->getQuotedName($this);
2072 9702
2073
        $sql = [];
2074 9702
2075
        if ($this->supportsForeignKeyConstraints()) {
2076 9702
            foreach ($diff->addedForeignKeys as $foreignKey) {
2077 9702
                $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
2078 592
            }
2079
2080
            foreach ($diff->changedForeignKeys as $foreignKey) {
2081 9702
                $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName);
2082 540
            }
2083
        }
2084
2085
        foreach ($diff->addedIndexes as $index) {
2086 9702
            $sql[] = $this->getCreateIndexSQL($index, $tableName);
2087 107
        }
2088
2089
        foreach ($diff->changedIndexes as $index) {
2090 9702
            $sql[] = $this->getCreateIndexSQL($index, $tableName);
2091 484
        }
2092
2093
        foreach ($diff->renamedIndexes as $oldIndexName => $index) {
2094 9702
            $oldIndexName = new Identifier($oldIndexName);
2095 2347
            $sql          = array_merge(
2096 2347
                $sql,
2097 2347
                $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableName)
2098 2347
            );
2099
        }
2100
2101
        return $sql;
2102 9702
    }
2103
2104
    /**
2105
     * Returns the SQL for renaming an index on a table.
2106
     *
2107
     * @param string $oldIndexName The name of the index to rename from.
2108
     * @param Index  $index        The definition of the index to rename to.
2109
     * @param string $tableName    The table to rename the given index on.
2110
     *
2111
     * @return string[] The sequence of SQL statements for renaming the given index.
2112
     */
2113
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
2114 290
    {
2115
        return [
2116
            $this->getDropIndexSQL($oldIndexName, $tableName),
2117 290
            $this->getCreateIndexSQL($index, $tableName),
2118 290
        ];
2119
    }
2120
2121
    /**
2122
     * Common code for alter table statement generation that updates the changed Index and Foreign Key definitions.
2123
     *
2124
     * @return string[]
2125
     */
2126
    protected function _getAlterTableIndexForeignKeySQL(TableDiff $diff)
2127
    {
2128
        return array_merge($this->getPreAlterTableIndexForeignKeySQL($diff), $this->getPostAlterTableIndexForeignKeySQL($diff));
2129
    }
2130
2131
    /**
2132
     * Gets declaration of a number of fields in bulk.
2133
     *
2134
     * @param mixed[][] $fields A multidimensional associative array.
2135
     *                          The first dimension determines the field name, while the second
2136
     *                          dimension is keyed with the name of the properties
2137
     *                          of the field being declared as array indexes. Currently, the types
2138
     *                          of supported field properties are as follows:
2139
     *
2140
     *      length
2141
     *          Integer value that determines the maximum length of the text
2142
     *          field. If this argument is missing the field should be
2143
     *          declared to have the longest length allowed by the DBMS.
2144
     *
2145
     *      default
2146
     *          Text value to be used as default for this field.
2147
     *
2148
     *      notnull
2149
     *          Boolean flag that indicates whether this field is constrained
2150
     *          to not be set to null.
2151
     *      charset
2152
     *          Text value with the default CHARACTER SET for this field.
2153
     *      collation
2154
     *          Text value with the default COLLATION for this field.
2155
     *      unique
2156
     *          unique constraint
2157
     *
2158
     * @return string
2159
     */
2160
    public function getColumnDeclarationListSQL(array $fields)
2161 11477
    {
2162
        $queryFields = [];
2163 11477
2164
        foreach ($fields as $fieldName => $field) {
2165 11477
            $queryFields[] = $this->getColumnDeclarationSQL($fieldName, $field);
2166 11477
        }
2167
2168
        return implode(', ', $queryFields);
2169 11477
    }
2170
2171
    /**
2172
     * Obtains DBMS specific SQL code portion needed to declare a generic type
2173
     * field to be used in statements like CREATE TABLE.
2174
     *
2175
     * @param string  $name  The name the field to be declared.
2176
     * @param mixed[] $field An associative array with the name of the properties
2177
     *                       of the field being declared as array indexes. Currently, the types
2178
     *                       of supported field properties are as follows:
2179
     *
2180
     *      length
2181
     *          Integer value that determines the maximum length of the text
2182
     *          field. If this argument is missing the field should be
2183
     *          declared to have the longest length allowed by the DBMS.
2184
     *
2185
     *      default
2186
     *          Text value to be used as default for this field.
2187
     *
2188
     *      notnull
2189
     *          Boolean flag that indicates whether this field is constrained
2190
     *          to not be set to null.
2191
     *      charset
2192
     *          Text value with the default CHARACTER SET for this field.
2193
     *      collation
2194
     *          Text value with the default COLLATION for this field.
2195
     *      unique
2196
     *          unique constraint
2197
     *      check
2198
     *          column check constraint
2199
     *      columnDefinition
2200
     *          a string that defines the complete column
2201
     *
2202
     * @return string DBMS specific SQL code portion that should be used to declare the column.
2203
     */
2204
    public function getColumnDeclarationSQL($name, array $field)
2205 12167
    {
2206
        if (isset($field['columnDefinition'])) {
2207 12167
            $columnDef = $this->getCustomTypeDeclarationSQL($field);
2208 393
        } else {
2209
            $default = $this->getDefaultValueDeclarationSQL($field);
2210 11789
2211
            $charset = isset($field['charset']) && $field['charset'] ?
2212 11789
                ' ' . $this->getColumnCharsetDeclarationSQL($field['charset']) : '';
2213 11789
2214
            $collation = isset($field['collation']) && $field['collation'] ?
2215 11789
                ' ' . $this->getColumnCollationDeclarationSQL($field['collation']) : '';
2216 11789
2217
            $notnull = isset($field['notnull']) && $field['notnull'] ? ' NOT NULL' : '';
2218 11789
2219
            $unique = isset($field['unique']) && $field['unique'] ?
2220 11789
                ' ' . $this->getUniqueFieldDeclarationSQL() : '';
2221 11789
2222
            $check = isset($field['check']) && $field['check'] ?
2223 11789
                ' ' . $field['check'] : '';
2224 11789
2225
            $typeDecl  = $field['type']->getSQLDeclaration($field, $this);
2226 11789
            $columnDef = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation;
2227 11789
2228
            if ($this->supportsInlineColumnComments() && isset($field['comment']) && $field['comment'] !== '') {
2229 11789
                $columnDef .= ' ' . $this->getInlineColumnCommentSQL($field['comment']);
2230 1067
            }
2231
        }
2232
2233
        return $name . ' ' . $columnDef;
2234 12167
    }
2235
2236
    /**
2237
     * Returns the SQL snippet that declares a floating point column of arbitrary precision.
2238
     *
2239
     * @param mixed[] $columnDef
2240
     *
2241
     * @return string
2242
     */
2243
    public function getDecimalTypeDeclarationSQL(array $columnDef)
2244 3627
    {
2245
        $columnDef['precision'] = ! isset($columnDef['precision']) || empty($columnDef['precision'])
2246 3627
            ? 10 : $columnDef['precision'];
2247 3627
        $columnDef['scale']     = ! isset($columnDef['scale']) || empty($columnDef['scale'])
2248 3627
            ? 0 : $columnDef['scale'];
2249 3627
2250
        return 'NUMERIC(' . $columnDef['precision'] . ', ' . $columnDef['scale'] . ')';
2251 3627
    }
2252
2253
    /**
2254
     * Obtains DBMS specific SQL code portion needed to set a default value
2255
     * declaration to be used in statements like CREATE TABLE.
2256
     *
2257
     * @param mixed[] $field The field definition array.
2258
     *
2259
     * @return string DBMS specific SQL code portion needed to set a default value.
2260
     */
2261
    public function getDefaultValueDeclarationSQL($field)
2262 13446
    {
2263
        if (! isset($field['default'])) {
2264 13446
            return empty($field['notnull']) ? ' DEFAULT NULL' : '';
2265 11576
        }
2266
2267
        $default = $field['default'];
2268 2505
2269
        if (! isset($field['type'])) {
2270 2505
            return " DEFAULT '" . $default . "'";
2271
        }
2272
2273
        $type = $field['type'];
2274 2505
2275
        if ($type instanceof Types\PhpIntegerMappingType) {
2276 2505
            return ' DEFAULT ' . $default;
2277 699
        }
2278
2279
        if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) {
2280 1877
            return ' DEFAULT ' . $this->getCurrentTimestampSQL();
2281 437
        }
2282
2283
        if ($type instanceof Types\TimeType && $default === $this->getCurrentTimeSQL()) {
2284 1458
            return ' DEFAULT ' . $this->getCurrentTimeSQL();
2285 4
        }
2286
2287
        if ($type instanceof Types\DateType && $default === $this->getCurrentDateSQL()) {
2288 1458
            return ' DEFAULT ' . $this->getCurrentDateSQL();
2289 409
        }
2290
2291
        if ($type instanceof Types\BooleanType) {
2292 1049
            return " DEFAULT '" . $this->convertBooleans($default) . "'";
2293 437
        }
2294
2295
        return " DEFAULT '" . $default . "'";
2296 1017
    }
2297
2298
    /**
2299
     * Obtains DBMS specific SQL code portion needed to set a CHECK constraint
2300
     * declaration to be used in statements like CREATE TABLE.
2301
     *
2302
     * @param string[]|mixed[][] $definition The check definition.
2303
     *
2304
     * @return string DBMS specific SQL code portion needed to set a CHECK constraint.
2305
     */
2306
    public function getCheckDeclarationSQL(array $definition)
2307 3986
    {
2308
        $constraints = [];
2309 3986
        foreach ($definition as $field => $def) {
2310 3986
            if (is_string($def)) {
2311 3986
                $constraints[] = 'CHECK (' . $def . ')';
2312
            } else {
2313
                if (isset($def['min'])) {
2314 3986
                    $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')';
2315 135
                }
2316
2317
                if (isset($def['max'])) {
2318 3986
                    $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')';
2319 641
                }
2320
            }
2321
        }
2322
2323
        return implode(', ', $constraints);
2324 3986
    }
2325
2326
    /**
2327
     * Obtains DBMS specific SQL code portion needed to set a unique
2328
     * constraint declaration to be used in statements like CREATE TABLE.
2329
     *
2330
     * @param string $name  The name of the unique constraint.
2331
     * @param Index  $index The index definition.
2332
     *
2333
     * @return string DBMS specific SQL code portion needed to set a constraint.
2334
     *
2335
     * @throws InvalidArgumentException
2336
     */
2337
    public function getUniqueConstraintDeclarationSQL($name, Index $index)
2338 756
    {
2339
        $columns = $index->getColumns();
2340 756
        $name    = new Identifier($name);
2341 756
2342
        if (count($columns) === 0) {
2343 756
            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
2344
        }
2345
2346
        return 'CONSTRAINT ' . $name->getQuotedName($this) . ' UNIQUE ('
2347 756
            . $this->getIndexFieldDeclarationListSQL($index)
2348 756
            . ')' . $this->getPartialIndexSQL($index);
2349 756
    }
2350
2351
    /**
2352
     * Obtains DBMS specific SQL code portion needed to set an index
2353
     * declaration to be used in statements like CREATE TABLE.
2354
     *
2355
     * @param string $name  The name of the index.
2356
     * @param Index  $index The index definition.
2357
     *
2358
     * @return string DBMS specific SQL code portion needed to set an index.
2359
     *
2360
     * @throws InvalidArgumentException
2361
     */
2362
    public function getIndexDeclarationSQL($name, Index $index)
2363 1494
    {
2364
        $columns = $index->getColumns();
2365 1494
        $name    = new Identifier($name);
2366 1494
2367
        if (count($columns) === 0) {
2368 1494
            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
2369
        }
2370
2371
        return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name->getQuotedName($this) . ' ('
2372 1494
            . $this->getIndexFieldDeclarationListSQL($index)
2373 1494
            . ')' . $this->getPartialIndexSQL($index);
2374 1494
    }
2375
2376
    /**
2377
     * Obtains SQL code portion needed to create a custom column,
2378
     * e.g. when a field has the "columnDefinition" keyword.
2379
     * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate.
2380
     *
2381
     * @param mixed[] $columnDef
2382
     *
2383
     * @return string
2384
     */
2385
    public function getCustomTypeDeclarationSQL(array $columnDef)
2386 501
    {
2387
        return $columnDef['columnDefinition'];
2388 501
    }
2389
2390
    /**
2391
     * Obtains DBMS specific SQL code portion needed to set an index
2392
     * declaration to be used in statements like CREATE TABLE.
2393
     *
2394
     * @param mixed[]|Index $columnsOrIndex array declaration is deprecated, prefer passing Index to this method
2395
     */
2396
    public function getIndexFieldDeclarationListSQL($columnsOrIndex) : string
2397 7149
    {
2398
        if ($columnsOrIndex instanceof Index) {
2399 7149
            return implode(', ', $columnsOrIndex->getQuotedColumns($this));
2400 5961
        }
2401
2402
        if (! is_array($columnsOrIndex)) {
2403 1350
            throw new InvalidArgumentException('Fields argument should be an Index or array.');
2404
        }
2405
2406
        $ret = [];
2407 1350
2408
        foreach ($columnsOrIndex as $column => $definition) {
2409 1350
            if (is_array($definition)) {
2410 1350
                $ret[] = $column;
2411
            } else {
2412
                $ret[] = $definition;
2413 1350
            }
2414
        }
2415
2416
        return implode(', ', $ret);
2417 1350
    }
2418
2419
    /**
2420
     * Returns the required SQL string that fits between CREATE ... TABLE
2421
     * to create the table as a temporary table.
2422
     *
2423
     * Should be overridden in driver classes to return the correct string for the
2424
     * specific database type.
2425
     *
2426
     * The default is to return the string "TEMPORARY" - this will result in a
2427
     * SQL error for any database that does not support temporary tables, or that
2428
     * requires a different SQL command from "CREATE TEMPORARY TABLE".
2429
     *
2430
     * @return string The string required to be placed between "CREATE" and "TABLE"
2431
     *                to generate a temporary table, if possible.
2432
     */
2433
    public function getTemporaryTableSQL()
2434
    {
2435
        return 'TEMPORARY';
2436
    }
2437
2438
    /**
2439
     * Some vendors require temporary table names to be qualified specially.
2440
     *
2441
     * @param string $tableName
2442
     *
2443
     * @return string
2444
     */
2445
    public function getTemporaryTableName($tableName)
2446 42
    {
2447
        return $tableName;
2448 42
    }
2449
2450
    /**
2451
     * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
2452
     * of a field declaration to be used in statements like CREATE TABLE.
2453
     *
2454
     * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
2455
     *                of a field declaration.
2456
     */
2457
    public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey)
2458 3331
    {
2459
        $sql  = $this->getForeignKeyBaseDeclarationSQL($foreignKey);
2460 3331
        $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey);
2461 3007
2462
        return $sql;
2463 3007
    }
2464
2465
    /**
2466
     * Returns the FOREIGN KEY query section dealing with non-standard options
2467
     * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
2468
     *
2469
     * @param ForeignKeyConstraint $foreignKey The foreign key definition.
2470
     *
2471
     * @return string
2472
     */
2473
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
2474 2875
    {
2475
        $query = '';
2476 2875
        if ($this->supportsForeignKeyOnUpdate() && $foreignKey->hasOption('onUpdate')) {
2477 2875
            $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate'));
2478 108
        }
2479
        if ($foreignKey->hasOption('onDelete')) {
2480 2875
            $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete'));
2481 267
        }
2482
2483
        return $query;
2484 2875
    }
2485
2486
    /**
2487
     * Returns the given referential action in uppercase if valid, otherwise throws an exception.
2488
     *
2489
     * @param string $action The foreign key referential action.
2490
     *
2491
     * @return string
2492
     *
2493
     * @throws InvalidArgumentException If unknown referential action given.
2494
     */
2495
    public function getForeignKeyReferentialActionSQL($action)
2496 3291
    {
2497
        $upper = strtoupper($action);
2498 3291
        switch ($upper) {
2499 3291
            case 'CASCADE':
2500 3291
            case 'SET NULL':
2501 2221
            case 'NO ACTION':
2502 1654
            case 'RESTRICT':
2503 1303
            case 'SET DEFAULT':
2504 925
                return $upper;
2505 2832
            default:
2506
                throw new InvalidArgumentException('Invalid foreign key action: ' . $upper);
2507 459
        }
2508
    }
2509
2510
    /**
2511
     * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
2512
     * of a field declaration to be used in statements like CREATE TABLE.
2513
     *
2514
     * @return string
2515
     *
2516
     * @throws InvalidArgumentException
2517
     */
2518
    public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey)
2519 2251
    {
2520
        $sql = '';
2521 2251
        if (strlen($foreignKey->getName())) {
2522 2251
            $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' ';
2523 1873
        }
2524
        $sql .= 'FOREIGN KEY (';
2525 2251
2526
        if (count($foreignKey->getLocalColumns()) === 0) {
2527 2251
            throw new InvalidArgumentException("Incomplete definition. 'local' required.");
2528
        }
2529
        if (count($foreignKey->getForeignColumns()) === 0) {
2530 2251
            throw new InvalidArgumentException("Incomplete definition. 'foreign' required.");
2531
        }
2532
        if (strlen($foreignKey->getForeignTableName()) === 0) {
2533 2251
            throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required.");
2534
        }
2535
2536
        return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this))
2537 2251
            . ') REFERENCES '
2538 2251
            . $foreignKey->getQuotedForeignTableName($this) . ' ('
2539 2251
            . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')';
2540 2251
    }
2541
2542
    /**
2543
     * Obtains DBMS specific SQL code portion needed to set the UNIQUE constraint
2544
     * of a field declaration to be used in statements like CREATE TABLE.
2545
     *
2546
     * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint
2547
     *                of a field declaration.
2548
     */
2549
    public function getUniqueFieldDeclarationSQL()
2550
    {
2551
        return 'UNIQUE';
2552
    }
2553
2554
    /**
2555
     * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET
2556
     * of a field declaration to be used in statements like CREATE TABLE.
2557
     *
2558
     * @param string $charset The name of the charset.
2559
     *
2560
     * @return string DBMS specific SQL code portion needed to set the CHARACTER SET
2561
     *                of a field declaration.
2562
     */
2563
    public function getColumnCharsetDeclarationSQL($charset)
2564
    {
2565
        return '';
2566
    }
2567
2568
    /**
2569
     * Obtains DBMS specific SQL code portion needed to set the COLLATION
2570
     * of a field declaration to be used in statements like CREATE TABLE.
2571
     *
2572
     * @param string $collation The name of the collation.
2573
     *
2574
     * @return string DBMS specific SQL code portion needed to set the COLLATION
2575
     *                of a field declaration.
2576
     */
2577
    public function getColumnCollationDeclarationSQL($collation)
2578 395
    {
2579
        return $this->supportsColumnCollation() ? 'COLLATE ' . $collation : '';
2580 395
    }
2581
2582
    /**
2583
     * Whether the platform prefers sequences for ID generation.
2584
     * Subclasses should override this method to return TRUE if they prefer sequences.
2585
     *
2586
     * @return bool
2587
     */
2588
    public function prefersSequences()
2589 135
    {
2590
        return false;
2591 135
    }
2592
2593
    /**
2594
     * Whether the platform prefers identity columns (eg. autoincrement) for ID generation.
2595
     * Subclasses should override this method to return TRUE if they prefer identity columns.
2596
     *
2597
     * @return bool
2598
     */
2599
    public function prefersIdentityColumns()
2600 162
    {
2601
        return false;
2602 162
    }
2603
2604
    /**
2605
     * Some platforms need the boolean values to be converted.
2606
     *
2607
     * The default conversion in this implementation converts to integers (false => 0, true => 1).
2608
     *
2609
     * Note: if the input is not a boolean the original input might be returned.
2610
     *
2611
     * There are two contexts when converting booleans: Literals and Prepared Statements.
2612
     * This method should handle the literal case
2613
     *
2614
     * @param mixed $item A boolean or an array of them.
2615
     *
2616
     * @return mixed A boolean database value or an array of them.
2617
     */
2618
    public function convertBooleans($item)
2619 621
    {
2620
        if (is_array($item)) {
2621 621
            foreach ($item as $k => $value) {
2622
                if (! is_bool($value)) {
2623
                    continue;
2624
                }
2625
2626
                $item[$k] = (int) $value;
2627
            }
2628
        } elseif (is_bool($item)) {
2629 621
            $item = (int) $item;
2630 594
        }
2631
2632
        return $item;
2633 621
    }
2634
2635
    /**
2636
     * Some platforms have boolean literals that needs to be correctly converted
2637
     *
2638
     * The default conversion tries to convert value into bool "(bool)$item"
2639
     *
2640
     * @param mixed $item
2641
     *
2642
     * @return bool|null
2643
     */
2644
    public function convertFromBoolean($item)
2645 1323
    {
2646
        return $item === null ? null: (bool) $item;
2647 1323
    }
2648
2649
    /**
2650
     * This method should handle the prepared statements case. When there is no
2651
     * distinction, it's OK to use the same method.
2652
     *
2653
     * Note: if the input is not a boolean the original input might be returned.
2654
     *
2655
     * @param mixed $item A boolean or an array of them.
2656
     *
2657
     * @return mixed A boolean database value or an array of them.
2658
     */
2659
    public function convertBooleansToDatabaseValue($item)
2660 202
    {
2661
        return $this->convertBooleans($item);
2662 202
    }
2663
2664
    /**
2665
     * Returns the SQL specific for the platform to get the current date.
2666
     *
2667
     * @return string
2668
     */
2669
    public function getCurrentDateSQL()
2670 323
    {
2671
        return 'CURRENT_DATE';
2672 323
    }
2673
2674
    /**
2675
     * Returns the SQL specific for the platform to get the current time.
2676
     *
2677
     * @return string
2678
     */
2679
    public function getCurrentTimeSQL()
2680 12
    {
2681
        return 'CURRENT_TIME';
2682 12
    }
2683
2684
    /**
2685
     * Returns the SQL specific for the platform to get the current timestamp
2686
     *
2687
     * @return string
2688
     */
2689
    public function getCurrentTimestampSQL()
2690 474
    {
2691
        return 'CURRENT_TIMESTAMP';
2692 474
    }
2693
2694
    /**
2695
     * Returns the SQL for a given transaction isolation level Connection constant.
2696
     *
2697
     * @param int $level
2698
     *
2699
     * @return string
2700
     *
2701
     * @throws InvalidArgumentException
2702
     */
2703
    protected function _getTransactionIsolationLevelSQL($level)
2704 297
    {
2705
        switch ($level) {
2706 297
            case TransactionIsolationLevel::READ_UNCOMMITTED:
2707
                return 'READ UNCOMMITTED';
2708 297
            case TransactionIsolationLevel::READ_COMMITTED:
2709
                return 'READ COMMITTED';
2710 297
            case TransactionIsolationLevel::REPEATABLE_READ:
2711
                return 'REPEATABLE READ';
2712 297
            case TransactionIsolationLevel::SERIALIZABLE:
2713
                return 'SERIALIZABLE';
2714 297
            default:
2715
                throw new InvalidArgumentException('Invalid isolation level:' . $level);
2716
        }
2717
    }
2718
2719
    /**
2720
     * @return string
2721
     *
2722
     * @throws DBALException If not supported on this platform.
2723
     */
2724
    public function getListDatabasesSQL()
2725 1
    {
2726
        throw DBALException::notSupported(__METHOD__);
2727 1
    }
2728
2729
    /**
2730
     * Returns the SQL statement for retrieving the namespaces defined in the database.
2731
     *
2732
     * @return string
2733
     *
2734
     * @throws DBALException If not supported on this platform.
2735
     */
2736
    public function getListNamespacesSQL()
2737
    {
2738
        throw DBALException::notSupported(__METHOD__);
2739
    }
2740
2741
    /**
2742
     * @param string $database
2743
     *
2744
     * @return string
2745
     *
2746
     * @throws DBALException If not supported on this platform.
2747
     */
2748
    public function getListSequencesSQL($database)
2749
    {
2750
        throw DBALException::notSupported(__METHOD__);
2751
    }
2752
2753
    /**
2754
     * @param string $table
2755
     *
2756
     * @return string
2757
     *
2758
     * @throws DBALException If not supported on this platform.
2759
     */
2760
    public function getListTableConstraintsSQL($table)
2761
    {
2762
        throw DBALException::notSupported(__METHOD__);
2763
    }
2764
2765
    /**
2766
     * @param string      $table
2767
     * @param string|null $database
2768
     *
2769
     * @return string
2770
     *
2771
     * @throws DBALException If not supported on this platform.
2772
     */
2773
    public function getListTableColumnsSQL($table, $database = null)
2774
    {
2775
        throw DBALException::notSupported(__METHOD__);
2776
    }
2777
2778
    /**
2779
     * @return string
2780
     *
2781
     * @throws DBALException If not supported on this platform.
2782
     */
2783
    public function getListTablesSQL()
2784
    {
2785
        throw DBALException::notSupported(__METHOD__);
2786
    }
2787
2788
    /**
2789
     * @return string
2790
     *
2791
     * @throws DBALException If not supported on this platform.
2792
     */
2793
    public function getListUsersSQL()
2794
    {
2795
        throw DBALException::notSupported(__METHOD__);
2796
    }
2797
2798
    /**
2799
     * Returns the SQL to list all views of a database or user.
2800
     *
2801
     * @param string $database
2802
     *
2803
     * @return string
2804
     *
2805
     * @throws DBALException If not supported on this platform.
2806
     */
2807
    public function getListViewsSQL($database)
2808
    {
2809
        throw DBALException::notSupported(__METHOD__);
2810
    }
2811
2812
    /**
2813
     * Returns the list of indexes for the current database.
2814
     *
2815
     * The current database parameter is optional but will always be passed
2816
     * when using the SchemaManager API and is the database the given table is in.
2817
     *
2818
     * Attention: Some platforms only support currentDatabase when they
2819
     * are connected with that database. Cross-database information schema
2820
     * requests may be impossible.
2821
     *
2822
     * @param string $table
2823
     * @param string $currentDatabase
2824
     *
2825
     * @return string
2826
     *
2827
     * @throws DBALException If not supported on this platform.
2828
     */
2829
    public function getListTableIndexesSQL($table, $currentDatabase = null)
2830
    {
2831
        throw DBALException::notSupported(__METHOD__);
2832
    }
2833
2834
    /**
2835
     * @param string $table
2836
     *
2837
     * @return string
2838
     *
2839
     * @throws DBALException If not supported on this platform.
2840
     */
2841
    public function getListTableForeignKeysSQL($table)
2842
    {
2843
        throw DBALException::notSupported(__METHOD__);
2844
    }
2845
2846
    /**
2847
     * @param string $name
2848
     * @param string $sql
2849
     *
2850
     * @return string
2851
     *
2852
     * @throws DBALException If not supported on this platform.
2853
     */
2854
    public function getCreateViewSQL($name, $sql)
2855
    {
2856
        throw DBALException::notSupported(__METHOD__);
2857
    }
2858
2859
    /**
2860
     * @param string $name
2861
     *
2862
     * @return string
2863
     *
2864
     * @throws DBALException If not supported on this platform.
2865
     */
2866
    public function getDropViewSQL($name)
2867
    {
2868
        throw DBALException::notSupported(__METHOD__);
2869
    }
2870
2871
    /**
2872
     * Returns the SQL snippet to drop an existing sequence.
2873
     *
2874
     * @param Sequence|string $sequence
2875
     *
2876
     * @return string
2877
     *
2878
     * @throws DBALException If not supported on this platform.
2879
     */
2880
    public function getDropSequenceSQL($sequence)
2881
    {
2882
        throw DBALException::notSupported(__METHOD__);
2883
    }
2884
2885
    /**
2886
     * @param string $sequenceName
2887
     *
2888
     * @return string
2889
     *
2890
     * @throws DBALException If not supported on this platform.
2891
     */
2892
    public function getSequenceNextValSQL($sequenceName)
2893
    {
2894
        throw DBALException::notSupported(__METHOD__);
2895
    }
2896
2897
    /**
2898
     * Returns the SQL to create a new database.
2899
     *
2900
     * @param string $database The name of the database that should be created.
2901
     *
2902
     * @return string
2903
     *
2904
     * @throws DBALException If not supported on this platform.
2905
     */
2906
    public function getCreateDatabaseSQL($database)
2907 27
    {
2908
        throw DBALException::notSupported(__METHOD__);
2909 27
    }
2910
2911
    /**
2912
     * Returns the SQL to set the transaction isolation level.
2913
     *
2914
     * @param int $level
2915
     *
2916
     * @return string
2917
     *
2918
     * @throws DBALException If not supported on this platform.
2919
     */
2920
    public function getSetTransactionIsolationSQL($level)
2921
    {
2922
        throw DBALException::notSupported(__METHOD__);
2923
    }
2924
2925
    /**
2926
     * Obtains DBMS specific SQL to be used to create datetime fields in
2927
     * statements like CREATE TABLE.
2928
     *
2929
     * @param mixed[] $fieldDeclaration
2930
     *
2931
     * @return string
2932
     *
2933
     * @throws DBALException If not supported on this platform.
2934
     */
2935
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
2936
    {
2937
        throw DBALException::notSupported(__METHOD__);
2938
    }
2939
2940
    /**
2941
     * Obtains DBMS specific SQL to be used to create datetime with timezone offset fields.
2942
     *
2943
     * @param mixed[] $fieldDeclaration
2944
     *
2945
     * @return string
2946
     */
2947
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
2948 240
    {
2949
        return $this->getDateTimeTypeDeclarationSQL($fieldDeclaration);
2950 240
    }
2951
2952
2953
    /**
2954
     * Obtains DBMS specific SQL to be used to create date fields in statements
2955
     * like CREATE TABLE.
2956
     *
2957
     * @param mixed[] $fieldDeclaration
2958
     *
2959
     * @return string
2960
     *
2961
     * @throws DBALException If not supported on this platform.
2962
     */
2963
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
2964
    {
2965
        throw DBALException::notSupported(__METHOD__);
2966
    }
2967
2968
    /**
2969
     * Obtains DBMS specific SQL to be used to create time fields in statements
2970
     * like CREATE TABLE.
2971
     *
2972
     * @param mixed[] $fieldDeclaration
2973
     *
2974
     * @return string
2975
     *
2976
     * @throws DBALException If not supported on this platform.
2977
     */
2978
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
2979
    {
2980
        throw DBALException::notSupported(__METHOD__);
2981
    }
2982
2983
    /**
2984
     * @param mixed[] $fieldDeclaration
2985
     *
2986
     * @return string
2987
     */
2988
    public function getFloatDeclarationSQL(array $fieldDeclaration)
2989 2631
    {
2990
        return 'DOUBLE PRECISION';
2991 2631
    }
2992
2993
    /**
2994
     * Gets the default transaction isolation level of the platform.
2995
     *
2996
     * @see TransactionIsolationLevel
2997
     *
2998
     * @return int The default isolation level.
2999
     */
3000
    public function getDefaultTransactionIsolationLevel()
3001
    {
3002
        return TransactionIsolationLevel::READ_COMMITTED;
3003
    }
3004
3005
    /* supports*() methods */
3006
3007
    /**
3008
     * Whether the platform supports sequences.
3009
     *
3010
     * @return bool
3011
     */
3012
    public function supportsSequences()
3013 179
    {
3014
        return false;
3015 179
    }
3016
3017
    /**
3018
     * Whether the platform supports identity columns.
3019
     *
3020
     * Identity columns are columns that receive an auto-generated value from the
3021
     * database on insert of a row.
3022
     *
3023
     * @return bool
3024
     */
3025
    public function supportsIdentityColumns()
3026 29
    {
3027
        return false;
3028 29
    }
3029
3030
    /**
3031
     * Whether the platform emulates identity columns through sequences.
3032
     *
3033
     * Some platforms that do not support identity columns natively
3034
     * but support sequences can emulate identity columns by using
3035
     * sequences.
3036
     *
3037
     * @return bool
3038
     */
3039
    public function usesSequenceEmulatedIdentityColumns()
3040 342
    {
3041
        return false;
3042 342
    }
3043
3044
    /**
3045
     * Returns the name of the sequence for a particular identity column in a particular table.
3046
     *
3047
     * @see    usesSequenceEmulatedIdentityColumns
3048
     *
3049
     * @param string $tableName  The name of the table to return the sequence name for.
3050
     * @param string $columnName The name of the identity column in the table to return the sequence name for.
3051
     *
3052
     * @return string
3053
     *
3054
     * @throws DBALException If not supported on this platform.
3055
     */
3056
    public function getIdentitySequenceName($tableName, $columnName)
3057 324
    {
3058
        throw DBALException::notSupported(__METHOD__);
3059 324
    }
3060
3061
    /**
3062
     * Whether the platform supports indexes.
3063
     *
3064
     * @return bool
3065
     */
3066
    public function supportsIndexes()
3067 108
    {
3068
        return true;
3069 108
    }
3070
3071
    /**
3072
     * Whether the platform supports partial indexes.
3073
     *
3074
     * @return bool
3075
     */
3076
    public function supportsPartialIndexes()
3077 4053
    {
3078
        return false;
3079 4053
    }
3080
3081
    /**
3082
     * Whether the platform supports indexes with column length definitions.
3083
     */
3084
    public function supportsColumnLengthIndexes() : bool
3085 8162
    {
3086
        return false;
3087 8162
    }
3088
3089
    /**
3090
     * Whether the platform supports altering tables.
3091
     *
3092
     * @return bool
3093
     */
3094
    public function supportsAlterTable()
3095 135
    {
3096
        return true;
3097 135
    }
3098
3099
    /**
3100
     * Whether the platform supports transactions.
3101
     *
3102
     * @return bool
3103
     */
3104
    public function supportsTransactions()
3105 108
    {
3106
        return true;
3107 108
    }
3108
3109
    /**
3110
     * Whether the platform supports savepoints.
3111
     *
3112
     * @return bool
3113
     */
3114
    public function supportsSavepoints()
3115 592
    {
3116
        return true;
3117 592
    }
3118
3119
    /**
3120
     * Whether the platform supports releasing savepoints.
3121
     *
3122
     * @return bool
3123
     */
3124
    public function supportsReleaseSavepoints()
3125 130
    {
3126
        return $this->supportsSavepoints();
3127 130
    }
3128
3129
    /**
3130
     * Whether the platform supports primary key constraints.
3131
     *
3132
     * @return bool
3133
     */
3134
    public function supportsPrimaryConstraints()
3135 108
    {
3136
        return true;
3137 108
    }
3138
3139
    /**
3140
     * Whether the platform supports foreign key constraints.
3141
     *
3142
     * @return bool
3143
     */
3144
    public function supportsForeignKeyConstraints()
3145 13042
    {
3146
        return true;
3147 13042
    }
3148
3149
    /**
3150
     * Whether this platform supports onUpdate in foreign key constraints.
3151
     *
3152
     * @return bool
3153
     */
3154
    public function supportsForeignKeyOnUpdate()
3155 2983
    {
3156
        return $this->supportsForeignKeyConstraints();
3157 2983
    }
3158
3159
    /**
3160
     * Whether the platform supports database schemas.
3161
     *
3162
     * @return bool
3163
     */
3164
    public function supportsSchemas()
3165 289
    {
3166
        return false;
3167 289
    }
3168
3169
    /**
3170
     * Whether this platform can emulate schemas.
3171
     *
3172
     * Platforms that either support or emulate schemas don't automatically
3173
     * filter a schema for the namespaced elements in {@link
3174
     * AbstractManager#createSchema}.
3175
     *
3176
     * @return bool
3177
     */
3178
    public function canEmulateSchemas()
3179 108
    {
3180
        return false;
3181 108
    }
3182
3183
    /**
3184
     * Returns the default schema name.
3185
     *
3186
     * @return string
3187
     *
3188
     * @throws DBALException If not supported on this platform.
3189
     */
3190
    public function getDefaultSchemaName()
3191
    {
3192
        throw DBALException::notSupported(__METHOD__);
3193
    }
3194
3195
    /**
3196
     * Whether this platform supports create database.
3197
     *
3198
     * Some databases don't allow to create and drop databases at all or only with certain tools.
3199
     *
3200
     * @return bool
3201
     */
3202
    public function supportsCreateDropDatabase()
3203 152
    {
3204
        return true;
3205 152
    }
3206
3207
    /**
3208
     * Whether the platform supports getting the affected rows of a recent update/delete type query.
3209
     *
3210
     * @return bool
3211
     */
3212
    public function supportsGettingAffectedRows()
3213 108
    {
3214
        return true;
3215 108
    }
3216
3217
    /**
3218
     * Whether this platform support to add inline column comments as postfix.
3219
     *
3220
     * @return bool
3221
     */
3222
    public function supportsInlineColumnComments()
3223 6620
    {
3224
        return false;
3225 6620
    }
3226
3227
    /**
3228
     * Whether this platform support the proprietary syntax "COMMENT ON asset".
3229
     *
3230
     * @return bool
3231
     */
3232
    public function supportsCommentOnStatement()
3233 6313
    {
3234
        return false;
3235 6313
    }
3236
3237
    /**
3238
     * Does this platform have native guid type.
3239
     *
3240
     * @return bool
3241
     */
3242
    public function hasNativeGuidType()
3243 8105
    {
3244
        return false;
3245 8105
    }
3246
3247
    /**
3248
     * Does this platform have native JSON type.
3249
     *
3250
     * @return bool
3251
     */
3252
    public function hasNativeJsonType()
3253 18928
    {
3254
        return false;
3255 18928
    }
3256
3257
    /**
3258
     * @deprecated
3259
     *
3260
     * @todo Remove in 3.0
3261
     */
3262
    public function getIdentityColumnNullInsertSQL()
3263
    {
3264
        return '';
3265
    }
3266
3267
    /**
3268
     * Whether this platform supports views.
3269
     *
3270
     * @return bool
3271
     */
3272
    public function supportsViews()
3273 27
    {
3274
        return true;
3275 27
    }
3276
3277
    /**
3278
     * Does this platform support column collation?
3279
     *
3280
     * @return bool
3281
     */
3282
    public function supportsColumnCollation()
3283
    {
3284
        return false;
3285
    }
3286
3287
    /**
3288
     * Gets the format string, as accepted by the date() function, that describes
3289
     * the format of a stored datetime value of this platform.
3290
     *
3291
     * @return string The format string.
3292
     */
3293
    public function getDateTimeFormatString()
3294 468
    {
3295
        return 'Y-m-d H:i:s';
3296 468
    }
3297
3298
    /**
3299
     * Gets the format string, as accepted by the date() function, that describes
3300
     * the format of a stored datetime with timezone value of this platform.
3301
     *
3302
     * @return string The format string.
3303
     */
3304
    public function getDateTimeTzFormatString()
3305 205
    {
3306
        return 'Y-m-d H:i:s';
3307 205
    }
3308
3309
    /**
3310
     * Gets the format string, as accepted by the date() function, that describes
3311
     * the format of a stored date value of this platform.
3312
     *
3313
     * @return string The format string.
3314
     */
3315
    public function getDateFormatString()
3316 185
    {
3317
        return 'Y-m-d';
3318 185
    }
3319
3320
    /**
3321
     * Gets the format string, as accepted by the date() function, that describes
3322
     * the format of a stored time value of this platform.
3323
     *
3324
     * @return string The format string.
3325
     */
3326
    public function getTimeFormatString()
3327 158
    {
3328
        return 'H:i:s';
3329 158
    }
3330
3331
    /**
3332
     * Adds an driver-specific LIMIT clause to the query.
3333
     *
3334
     * @param string   $query
3335
     * @param int|null $limit
3336
     * @param int|null $offset
3337
     *
3338
     * @return string
3339
     *
3340
     * @throws DBALException
3341
     */
3342
    final public function modifyLimitQuery($query, $limit, $offset = null)
3343 3780
    {
3344
        if ($limit !== null) {
3345 3780
            $limit = (int) $limit;
3346 3024
        }
3347
3348
        $offset = (int) $offset;
3349 3780
3350
        if ($offset < 0) {
3351 3780
            throw new DBALException(sprintf(
3352
                'Offset must be a positive integer or zero, %d given',
3353
                $offset
3354
            ));
3355
        }
3356
3357
        if ($offset > 0 && ! $this->supportsLimitOffset()) {
3358 3780
            throw new DBALException(sprintf(
3359
                'Platform %s does not support offset values in limit queries.',
3360
                $this->getName()
3361
            ));
3362
        }
3363
3364
        return $this->doModifyLimitQuery($query, $limit, $offset);
3365 3780
    }
3366
3367
    /**
3368
     * Adds an platform-specific LIMIT clause to the query.
3369
     *
3370
     * @param string   $query
3371
     * @param int|null $limit
3372
     * @param int|null $offset
3373
     *
3374
     * @return string
3375
     */
3376
    protected function doModifyLimitQuery($query, $limit, $offset)
3377 550
    {
3378
        if ($limit !== null) {
3379 550
            $query .= ' LIMIT ' . $limit;
3380 380
        }
3381
3382
        if ($offset > 0) {
3383 550
            $query .= ' OFFSET ' . $offset;
3384 56
        }
3385
3386
        return $query;
3387 550
    }
3388
3389
    /**
3390
     * Whether the database platform support offsets in modify limit clauses.
3391
     *
3392
     * @return bool
3393
     */
3394
    public function supportsLimitOffset()
3395 580
    {
3396
        return true;
3397 580
    }
3398
3399
    /**
3400
     * Gets the character casing of a column in an SQL result set of this platform.
3401
     *
3402
     * @param string $column The column name for which to get the correct character casing.
3403
     *
3404
     * @return string The column name in the character casing used in SQL result sets.
3405
     */
3406
    public function getSQLResultCasing($column)
3407
    {
3408
        return $column;
3409
    }
3410
3411
    /**
3412
     * Makes any fixes to a name of a schema element (table, sequence, ...) that are required
3413
     * by restrictions of the platform, like a maximum length.
3414
     *
3415
     * @param string $schemaElementName
3416
     *
3417
     * @return string
3418
     */
3419
    public function fixSchemaElementName($schemaElementName)
3420
    {
3421
        return $schemaElementName;
3422
    }
3423
3424
    /**
3425
     * Maximum length of any given database identifier, like tables or column names.
3426
     *
3427
     * @return int
3428
     */
3429
    public function getMaxIdentifierLength()
3430 460
    {
3431
        return 63;
3432 460
    }
3433
3434
    /**
3435
     * Returns the insert SQL for an empty insert statement.
3436
     *
3437
     * @param string $tableName
3438
     * @param string $identifierColumnName
3439
     *
3440
     * @return string
3441
     */
3442
    public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName)
3443 16
    {
3444
        return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (null)';
3445 16
    }
3446
3447
    /**
3448
     * Generates a Truncate Table SQL statement for a given table.
3449
     *
3450
     * Cascade is not supported on many platforms but would optionally cascade the truncate by
3451
     * following the foreign keys.
3452
     *
3453
     * @param string $tableName
3454
     * @param bool   $cascade
3455
     *
3456
     * @return string
3457
     */
3458
    public function getTruncateTableSQL($tableName, $cascade = false)
3459 207
    {
3460
        $tableIdentifier = new Identifier($tableName);
3461 207
3462
        return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this);
3463 207
    }
3464
3465
    /**
3466
     * This is for test reasons, many vendors have special requirements for dummy statements.
3467
     *
3468
     * @return string
3469
     */
3470
    public function getDummySelectSQL()
3471 233
    {
3472
        $expression = func_num_args() > 0 ? func_get_arg(0) : '1';
3473 233
3474
        return sprintf('SELECT %s', $expression);
3475 233
    }
3476
3477
    /**
3478
     * Returns the SQL to create a new savepoint.
3479
     *
3480
     * @param string $savepoint
3481
     *
3482
     * @return string
3483
     */
3484
    public function createSavePoint($savepoint)
3485 24
    {
3486
        return 'SAVEPOINT ' . $savepoint;
3487 24
    }
3488
3489
    /**
3490
     * Returns the SQL to release a savepoint.
3491
     *
3492
     * @param string $savepoint
3493
     *
3494
     * @return string
3495
     */
3496
    public function releaseSavePoint($savepoint)
3497 22
    {
3498
        return 'RELEASE SAVEPOINT ' . $savepoint;
3499 22
    }
3500
3501
    /**
3502
     * Returns the SQL to rollback a savepoint.
3503
     *
3504
     * @param string $savepoint
3505
     *
3506
     * @return string
3507
     */
3508
    public function rollbackSavePoint($savepoint)
3509 24
    {
3510
        return 'ROLLBACK TO SAVEPOINT ' . $savepoint;
3511 24
    }
3512
3513
    /**
3514
     * Returns the keyword list instance of this platform.
3515
     *
3516
     * @return KeywordList
3517
     *
3518
     * @throws DBALException If no keyword list is specified.
3519
     */
3520
    final public function getReservedKeywordsList()
3521 30789
    {
3522
        // Check for an existing instantiation of the keywords class.
3523
        if ($this->_keywords) {
3524 30789
            return $this->_keywords;
3525 28980
        }
3526
3527
        $class    = $this->getReservedKeywordsClass();
3528 26402
        $keywords = new $class();
3529 26402
        if (! $keywords instanceof KeywordList) {
3530 26402
            throw DBALException::notSupported(__METHOD__);
3531
        }
3532
3533
        // Store the instance so it doesn't need to be generated on every request.
3534
        $this->_keywords = $keywords;
3535 26402
3536
        return $keywords;
3537 26402
    }
3538
3539
    /**
3540
     * Returns the class name of the reserved keywords list.
3541
     *
3542
     * @return string
3543
     *
3544
     * @throws DBALException If not supported on this platform.
3545
     */
3546
    protected function getReservedKeywordsClass()
3547
    {
3548
        throw DBALException::notSupported(__METHOD__);
3549
    }
3550
3551
    /**
3552
     * Quotes a literal string.
3553
     * This method is NOT meant to fix SQL injections!
3554
     * It is only meant to escape this platform's string literal
3555
     * quote character inside the given literal string.
3556
     *
3557
     * @param string $str The literal string to be quoted.
3558
     *
3559
     * @return string The quoted literal string.
3560
     */
3561
    public function quoteStringLiteral($str)
3562 10535
    {
3563
        $c = $this->getStringLiteralQuoteCharacter();
3564 10535
3565
        return $c . str_replace($c, $c . $c, $str) . $c;
3566 10535
    }
3567
3568
    /**
3569
     * Gets the character used for string literal quoting.
3570
     *
3571
     * @return string
3572
     */
3573
    public function getStringLiteralQuoteCharacter()
3574 11021
    {
3575
        return "'";
3576 11021
    }
3577
3578
    /**
3579
     * Escapes metacharacters in a string intended to be used with a LIKE
3580
     * operator.
3581
     *
3582
     * @param string $inputString a literal, unquoted string
3583
     * @param string $escapeChar  should be reused by the caller in the LIKE
3584
     *                            expression.
3585
     */
3586
    final public function escapeStringForLike(string $inputString, string $escapeChar) : string
3587 513
    {
3588
        return preg_replace(
3589 513
            '~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u',
3590 513
            addcslashes($escapeChar, '\\') . '$1',
3591 513
            $inputString
3592 513
        );
3593
    }
3594
3595
    protected function getLikeWildcardCharacters() : string
3596 513
    {
3597
        return '%_';
3598 513
    }
3599
}
3600