Completed
Push — 3.0.x ( 0d9a8c...f82f5c )
by Sergei
25s queued 17s
created

SQLAnywherePlatform::getAdvancedIndexOptionsSQL()   B

Complexity

Conditions 11
Paths 7

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
c 0
b 0
f 0
dl 0
loc 23
rs 7.3166
cc 11
nc 7
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Doctrine\DBAL\Platforms;
4
5
use Doctrine\DBAL\DBALException;
6
use Doctrine\DBAL\LockMode;
7
use Doctrine\DBAL\Schema\Column;
8
use Doctrine\DBAL\Schema\ColumnDiff;
9
use Doctrine\DBAL\Schema\Constraint;
10
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
11
use Doctrine\DBAL\Schema\Identifier;
12
use Doctrine\DBAL\Schema\Index;
13
use Doctrine\DBAL\Schema\Sequence;
14
use Doctrine\DBAL\Schema\Table;
15
use Doctrine\DBAL\Schema\TableDiff;
16
use Doctrine\DBAL\TransactionIsolationLevel;
17
use InvalidArgumentException;
18
use UnexpectedValueException;
19
use function array_merge;
20
use function array_unique;
21
use function array_values;
22
use function count;
23
use function explode;
24
use function func_get_args;
25
use function get_class;
26
use function implode;
27
use function is_string;
28
use function preg_match;
29
use function sprintf;
30
use function strlen;
31
use function strpos;
32
use function strtoupper;
33
use function substr;
34
35
/**
36
 * The SQLAnywherePlatform provides the behavior, features and SQL dialect of the
37
 * SAP Sybase SQL Anywhere 16 database platform.
38
 */
39
class SQLAnywherePlatform extends AbstractPlatform
40
{
41
    public const FOREIGN_KEY_MATCH_SIMPLE        = 1;
42
    public const FOREIGN_KEY_MATCH_FULL          = 2;
43
    public const FOREIGN_KEY_MATCH_SIMPLE_UNIQUE = 129;
44
    public const FOREIGN_KEY_MATCH_FULL_UNIQUE   = 130;
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function appendLockHint($fromClause, $lockMode)
50
    {
51
        switch (true) {
52
            case $lockMode === LockMode::NONE:
53
                return $fromClause . ' WITH (NOLOCK)';
54
55
            case $lockMode === LockMode::PESSIMISTIC_READ:
56
                return $fromClause . ' WITH (UPDLOCK)';
57
58
            case $lockMode === LockMode::PESSIMISTIC_WRITE:
59
                return $fromClause . ' WITH (XLOCK)';
60
61
            default:
62
                return $fromClause;
63
        }
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     *
69
     * SQL Anywhere supports a maximum length of 128 bytes for identifiers.
70
     */
71
    public function fixSchemaElementName($schemaElementName)
72
    {
73
        $maxIdentifierLength = $this->getMaxIdentifierLength();
74
75
        if (strlen($schemaElementName) > $maxIdentifierLength) {
76
            return substr($schemaElementName, 0, $maxIdentifierLength);
77
        }
78
79
        return $schemaElementName;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
86
    {
87
        $query = '';
88
89
        if ($foreignKey->hasOption('match')) {
90
            $query = ' MATCH ' . $this->getForeignKeyMatchClauseSQL($foreignKey->getOption('match'));
91
        }
92
93
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
94
95
        if ($foreignKey->hasOption('check_on_commit') && (bool) $foreignKey->getOption('check_on_commit')) {
96
            $query .= ' CHECK ON COMMIT';
97
        }
98
99
        if ($foreignKey->hasOption('clustered') && (bool) $foreignKey->getOption('clustered')) {
100
            $query .= ' CLUSTERED';
101
        }
102
103
        if ($foreignKey->hasOption('for_olap_workload') && (bool) $foreignKey->getOption('for_olap_workload')) {
104
            $query .= ' FOR OLAP WORKLOAD';
105
        }
106
107
        return $query;
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113
    public function getAlterTableSQL(TableDiff $diff)
114
    {
115
        $sql          = [];
116
        $columnSql    = [];
117
        $commentsSQL  = [];
118
        $tableSql     = [];
119
        $alterClauses = [];
120
121
        foreach ($diff->addedColumns as $column) {
122
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
123
                continue;
124
            }
125
126
            $alterClauses[] = $this->getAlterTableAddColumnClause($column);
127
128
            $comment = $this->getColumnComment($column);
129
130
            if ($comment === null || $comment === '') {
131
                continue;
132
            }
133
134
            $commentsSQL[] = $this->getCommentOnColumnSQL(
135
                $diff->getName($this)->getQuotedName($this),
136
                $column->getQuotedName($this),
137
                $comment
138
            );
139
        }
140
141
        foreach ($diff->removedColumns as $column) {
142
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
143
                continue;
144
            }
145
146
            $alterClauses[] = $this->getAlterTableRemoveColumnClause($column);
147
        }
148
149
        foreach ($diff->changedColumns as $columnDiff) {
150
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
151
                continue;
152
            }
153
154
            $alterClause = $this->getAlterTableChangeColumnClause($columnDiff);
155
156
            if ($alterClause !== null) {
157
                $alterClauses[] = $alterClause;
158
            }
159
160
            if (! $columnDiff->hasChanged('comment')) {
161
                continue;
162
            }
163
164
            $column = $columnDiff->column;
165
166
            $commentsSQL[] = $this->getCommentOnColumnSQL(
167
                $diff->getName($this)->getQuotedName($this),
168
                $column->getQuotedName($this),
169
                $this->getColumnComment($column)
170
            );
171
        }
172
173
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
174
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
175
                continue;
176
            }
177
178
            $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' .
179
                $this->getAlterTableRenameColumnClause($oldColumnName, $column);
180
        }
181
182
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
183
            if (! empty($alterClauses)) {
184
                $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . implode(', ', $alterClauses);
185
            }
186
187
            $sql = array_merge($sql, $commentsSQL);
188
189
            $newName = $diff->getNewName();
190
191
            if ($newName !== false) {
192
                $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' .
193
                    $this->getAlterTableRenameTableClause($newName);
194
            }
195
196
            $sql = array_merge(
197
                $this->getPreAlterTableIndexForeignKeySQL($diff),
198
                $sql,
199
                $this->getPostAlterTableIndexForeignKeySQL($diff)
200
            );
201
        }
202
203
        return array_merge($sql, $tableSql, $columnSql);
204
    }
205
206
    /**
207
     * Returns the SQL clause for creating a column in a table alteration.
208
     *
209
     * @param Column $column The column to add.
210
     *
211
     * @return string
212
     */
213
    protected function getAlterTableAddColumnClause(Column $column)
214
    {
215
        return 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
216
    }
217
218
    /**
219
     * Returns the SQL clause for altering a table.
220
     *
221
     * @param Identifier $tableName The quoted name of the table to alter.
222
     *
223
     * @return string
224
     */
225
    protected function getAlterTableClause(Identifier $tableName)
226
    {
227
        return 'ALTER TABLE ' . $tableName->getQuotedName($this);
228
    }
229
230
    /**
231
     * Returns the SQL clause for dropping a column in a table alteration.
232
     *
233
     * @param Column $column The column to drop.
234
     *
235
     * @return string
236
     */
237
    protected function getAlterTableRemoveColumnClause(Column $column)
238
    {
239
        return 'DROP ' . $column->getQuotedName($this);
240
    }
241
242
    /**
243
     * Returns the SQL clause for renaming a column in a table alteration.
244
     *
245
     * @param string $oldColumnName The quoted name of the column to rename.
246
     * @param Column $column        The column to rename to.
247
     *
248
     * @return string
249
     */
250
    protected function getAlterTableRenameColumnClause($oldColumnName, Column $column)
251
    {
252
        $oldColumnName = new Identifier($oldColumnName);
253
254
        return 'RENAME ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this);
255
    }
256
257
    /**
258
     * Returns the SQL clause for renaming a table in a table alteration.
259
     *
260
     * @param Identifier $newTableName The quoted name of the table to rename to.
261
     *
262
     * @return string
263
     */
264
    protected function getAlterTableRenameTableClause(Identifier $newTableName)
265
    {
266
        return 'RENAME ' . $newTableName->getQuotedName($this);
267
    }
268
269
    /**
270
     * Returns the SQL clause for altering a column in a table alteration.
271
     *
272
     * This method returns null in case that only the column comment has changed.
273
     * Changes in column comments have to be handled differently.
274
     *
275
     * @param ColumnDiff $columnDiff The diff of the column to alter.
276
     *
277
     * @return string|null
278
     */
279
    protected function getAlterTableChangeColumnClause(ColumnDiff $columnDiff)
280
    {
281
        $column = $columnDiff->column;
282
283
        // Do not return alter clause if only comment has changed.
284
        if (! ($columnDiff->hasChanged('comment') && count($columnDiff->changedProperties) === 1)) {
285
            $columnAlterationClause = 'ALTER ' .
286
                $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
287
288
            if ($columnDiff->hasChanged('default') && $column->getDefault() === null) {
289
                $columnAlterationClause .= ', ALTER ' . $column->getQuotedName($this) . ' DROP DEFAULT';
290
            }
291
292
            return $columnAlterationClause;
293
        }
294
295
        return null;
296
    }
297
298
    /**
299
     * {@inheritdoc}
300
     */
301
    public function getBigIntTypeDeclarationSQL(array $columnDef)
302
    {
303
        $columnDef['integer_type'] = 'BIGINT';
304
305
        return $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
306
    }
307
308
    /**
309
     * {@inheritdoc}
310
     */
311
    public function getBinaryDefaultLength()
312
    {
313
        return 1;
314
    }
315
316
    /**
317
     * {@inheritdoc}
318
     */
319
    public function getBinaryMaxLength()
320
    {
321
        return 32767;
322
    }
323
324
    /**
325
     * {@inheritdoc}
326
     */
327
    public function getBlobTypeDeclarationSQL(array $field)
328
    {
329
        return 'LONG BINARY';
330
    }
331
332
    /**
333
     * {@inheritdoc}
334
     *
335
     * BIT type columns require an explicit NULL declaration
336
     * in SQL Anywhere if they shall be nullable.
337
     * Otherwise by just omitting the NOT NULL clause,
338
     * SQL Anywhere will declare them NOT NULL nonetheless.
339
     */
340
    public function getBooleanTypeDeclarationSQL(array $columnDef)
341
    {
342
        $nullClause = isset($columnDef['notnull']) && (bool) $columnDef['notnull'] === false ? ' NULL' : '';
343
344
        return 'BIT' . $nullClause;
345
    }
346
347
    /**
348
     * {@inheritdoc}
349
     */
350
    public function getClobTypeDeclarationSQL(array $field)
351
    {
352
        return 'TEXT';
353
    }
354
355
    /**
356
     * {@inheritdoc}
357
     */
358
    public function getCommentOnColumnSQL($tableName, $columnName, $comment)
359
    {
360
        $tableName  = new Identifier($tableName);
361
        $columnName = new Identifier($columnName);
362
        $comment    = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment);
363
364
        return sprintf(
365
            'COMMENT ON COLUMN %s.%s IS %s',
366
            $tableName->getQuotedName($this),
367
            $columnName->getQuotedName($this),
368
            $comment
369
        );
370
    }
371
372
    /**
373
     * {@inheritdoc}
374
     */
375
    public function getConcatExpression()
376
    {
377
        return 'STRING(' . implode(', ', (array) func_get_args()) . ')';
378
    }
379
380
    /**
381
     * {@inheritdoc}
382
     */
383
    public function getCreateConstraintSQL(Constraint $constraint, $table)
384
    {
385
        if ($constraint instanceof ForeignKeyConstraint) {
386
            return $this->getCreateForeignKeySQL($constraint, $table);
387
        }
388
389
        if ($table instanceof Table) {
390
            $table = $table->getQuotedName($this);
391
        }
392
393
        return 'ALTER TABLE ' . $table .
394
               ' ADD ' . $this->getTableConstraintDeclarationSQL($constraint, $constraint->getQuotedName($this));
395
    }
396
397
    /**
398
     * {@inheritdoc}
399
     */
400
    public function getCreateDatabaseSQL($database)
401
    {
402
        $database = new Identifier($database);
403
404
        return "CREATE DATABASE '" . $database->getName() . "'";
405
    }
406
407
    /**
408
     * {@inheritdoc}
409
     *
410
     * Appends SQL Anywhere specific flags if given.
411
     */
412
    public function getCreateIndexSQL(Index $index, $table)
413
    {
414
        return parent::getCreateIndexSQL($index, $table) . $this->getAdvancedIndexOptionsSQL($index);
415
    }
416
417
    /**
418
     * {@inheritdoc}
419
     */
420
    public function getCreatePrimaryKeySQL(Index $index, $table)
421
    {
422
        if ($table instanceof Table) {
423
            $table = $table->getQuotedName($this);
424
        }
425
426
        return 'ALTER TABLE ' . $table . ' ADD ' . $this->getPrimaryKeyDeclarationSQL($index);
427
    }
428
429
    /**
430
     * {@inheritdoc}
431
     */
432
    public function getCreateTemporaryTableSnippetSQL()
433
    {
434
        return 'CREATE ' . $this->getTemporaryTableSQL() . ' TABLE';
435
    }
436
437
    /**
438
     * {@inheritdoc}
439
     */
440
    public function getCreateViewSQL($name, $sql)
441
    {
442
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
443
    }
444
445
    /**
446
     * {@inheritdoc}
447
     */
448
    public function getCurrentDateSQL()
449
    {
450
        return 'CURRENT DATE';
451
    }
452
453
    /**
454
     * {@inheritdoc}
455
     */
456
    public function getCurrentTimeSQL()
457
    {
458
        return 'CURRENT TIME';
459
    }
460
461
    /**
462
     * {@inheritdoc}
463
     */
464
    public function getCurrentTimestampSQL()
465
    {
466
        return 'CURRENT TIMESTAMP';
467
    }
468
469
    /**
470
     * {@inheritdoc}
471
     */
472
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
473
    {
474
        $factorClause = '';
475
476
        if ($operator === '-') {
477
            $factorClause = '-1 * ';
478
        }
479
480
        return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')';
481
    }
482
483
    /**
484
     * {@inheritdoc}
485
     */
486
    public function getDateDiffExpression($date1, $date2)
487
    {
488
        return 'DATEDIFF(day, ' . $date2 . ', ' . $date1 . ')';
489
    }
490
491
    /**
492
     * {@inheritdoc}
493
     */
494
    public function getDateTimeFormatString()
495
    {
496
        return 'Y-m-d H:i:s.u';
497
    }
498
499
    /**
500
     * {@inheritdoc}
501
     */
502
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
503
    {
504
        return 'DATETIME';
505
    }
506
507
    /**
508
     * {@inheritdoc}
509
     */
510
    public function getDateTimeTzFormatString()
511
    {
512
        return 'Y-m-d H:i:s.uP';
513
    }
514
515
    /**
516
     * {@inheritdoc}
517
     */
518
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
519
    {
520
        return 'DATE';
521
    }
522
523
    /**
524
     * {@inheritdoc}
525
     */
526
    public function getDefaultTransactionIsolationLevel()
527
    {
528
        return TransactionIsolationLevel::READ_UNCOMMITTED;
529
    }
530
531
    /**
532
     * {@inheritdoc}
533
     */
534
    public function getDropDatabaseSQL($database)
535
    {
536
        $database = new Identifier($database);
537
538
        return "DROP DATABASE '" . $database->getName() . "'";
539
    }
540
541
    /**
542
     * {@inheritdoc}
543
     */
544
    public function getDropIndexSQL($index, $table = null)
545
    {
546
        if ($index instanceof Index) {
547
            $index = $index->getQuotedName($this);
548
        }
549
550
        if (! is_string($index)) {
551
            throw new InvalidArgumentException(
552
                'SQLAnywherePlatform::getDropIndexSQL() expects $index parameter to be string or ' . Index::class . '.'
553
            );
554
        }
555
556
        if (! isset($table)) {
557
            return 'DROP INDEX ' . $index;
558
        }
559
560
        if ($table instanceof Table) {
561
            $table = $table->getQuotedName($this);
562
        }
563
564
        if (! is_string($table)) {
565
            throw new InvalidArgumentException(
566
                'SQLAnywherePlatform::getDropIndexSQL() expects $table parameter to be string or ' . Index::class . '.'
567
            );
568
        }
569
570
        return 'DROP INDEX ' . $table . '.' . $index;
571
    }
572
573
    /**
574
     * {@inheritdoc}
575
     */
576
    public function getDropViewSQL($name)
577
    {
578
        return 'DROP VIEW ' . $name;
579
    }
580
581
    /**
582
     * {@inheritdoc}
583
     */
584
    public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey)
585
    {
586
        $sql              = '';
587
        $foreignKeyName   = $foreignKey->getName();
588
        $localColumns     = $foreignKey->getQuotedLocalColumns($this);
589
        $foreignColumns   = $foreignKey->getQuotedForeignColumns($this);
590
        $foreignTableName = $foreignKey->getQuotedForeignTableName($this);
591
592
        if (! empty($foreignKeyName)) {
593
            $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' ';
594
        }
595
596
        if (empty($localColumns)) {
597
            throw new InvalidArgumentException("Incomplete definition. 'local' required.");
598
        }
599
600
        if (empty($foreignColumns)) {
601
            throw new InvalidArgumentException("Incomplete definition. 'foreign' required.");
602
        }
603
604
        if (empty($foreignTableName)) {
605
            throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required.");
606
        }
607
608
        if ($foreignKey->hasOption('notnull') && (bool) $foreignKey->getOption('notnull')) {
609
            $sql .= 'NOT NULL ';
610
        }
611
612
        return $sql .
613
            'FOREIGN KEY (' . $this->getIndexFieldDeclarationListSQL($localColumns) . ') ' .
614
            'REFERENCES ' . $foreignKey->getQuotedForeignTableName($this) .
615
            ' (' . $this->getIndexFieldDeclarationListSQL($foreignColumns) . ')';
616
    }
617
618
    /**
619
     * Returns foreign key MATCH clause for given type.
620
     *
621
     * @param int $type The foreign key match type
622
     *
623
     * @return string
624
     *
625
     * @throws InvalidArgumentException If unknown match type given.
626
     */
627
    public function getForeignKeyMatchClauseSQL($type)
628
    {
629
        switch ((int) $type) {
630
            case self::FOREIGN_KEY_MATCH_SIMPLE:
631
                return 'SIMPLE';
632
633
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
634
            case self::FOREIGN_KEY_MATCH_FULL:
635
                return 'FULL';
636
637
                break;
638
            case self::FOREIGN_KEY_MATCH_SIMPLE_UNIQUE:
639
                return 'UNIQUE SIMPLE';
640
641
                break;
642
            case self::FOREIGN_KEY_MATCH_FULL_UNIQUE:
643
                return 'UNIQUE FULL';
644
            default:
645
                throw new InvalidArgumentException('Invalid foreign key match type: ' . $type);
646
        }
647
    }
648
649
    /**
650
     * {@inheritdoc}
651
     */
652
    public function getForeignKeyReferentialActionSQL($action)
653
    {
654
        // NO ACTION is not supported, therefore falling back to RESTRICT.
655
        if (strtoupper($action) === 'NO ACTION') {
656
            return 'RESTRICT';
657
        }
658
659
        return parent::getForeignKeyReferentialActionSQL($action);
660
    }
661
662
    /**
663
     * {@inheritdoc}
664
     */
665
    public function getForUpdateSQL()
666
    {
667
        return '';
668
    }
669
670
    /**
671
     * {@inheritdoc}
672
     *
673
     * @deprecated Use application-generated UUIDs instead
674
     */
675
    public function getGuidExpression()
676
    {
677
        return 'NEWID()';
678
    }
679
680
    /**
681
     * {@inheritdoc}
682
     */
683
    public function getGuidTypeDeclarationSQL(array $field)
684
    {
685
        return 'UNIQUEIDENTIFIER';
686
    }
687
688
    /**
689
     * {@inheritdoc}
690
     */
691
    public function getIndexDeclarationSQL($name, Index $index)
692
    {
693
        // Index declaration in statements like CREATE TABLE is not supported.
694
        throw DBALException::notSupported(__METHOD__);
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700
    public function getIntegerTypeDeclarationSQL(array $columnDef)
701
    {
702
        $columnDef['integer_type'] = 'INT';
703
704
        return $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
705
    }
706
707
    /**
708
     * {@inheritdoc}
709
     */
710
    public function getListDatabasesSQL()
711
    {
712
        return 'SELECT db_name(number) AS name FROM sa_db_list()';
713
    }
714
715
    /**
716
     * {@inheritdoc}
717
     */
718
    public function getListTableColumnsSQL($table, $database = null)
719
    {
720
        $user = 'USER_NAME()';
721
722
        if (strpos($table, '.') !== false) {
723
            [$user, $table] = explode('.', $table);
724
            $user           = $this->quoteStringLiteral($user);
725
        }
726
727
        return sprintf(
728
            <<<'SQL'
729
SELECT    col.column_name,
730
          COALESCE(def.user_type_name, def.domain_name) AS 'type',
731
          def.declared_width AS 'length',
732
          def.scale,
733
          CHARINDEX('unsigned', def.domain_name) AS 'unsigned',
734
          IF col.nulls = 'Y' THEN 0 ELSE 1 ENDIF AS 'notnull',
735
          col."default",
736
          def.is_autoincrement AS 'autoincrement',
737
          rem.remarks AS 'comment'
738
FROM      sa_describe_query('SELECT * FROM "%s"') AS def
739
JOIN      SYS.SYSTABCOL AS col
740
ON        col.table_id = def.base_table_id AND col.column_id = def.base_column_id
741
LEFT JOIN SYS.SYSREMARK AS rem
742
ON        col.object_id = rem.object_id
743
WHERE     def.base_owner_name = %s
744
ORDER BY  def.base_column_id ASC
745
SQL
746
            ,
747
            $table,
748
            $user
749
        );
750
    }
751
752
    /**
753
     * {@inheritdoc}
754
     *
755
     * @todo Where is this used? Which information should be retrieved?
756
     */
757
    public function getListTableConstraintsSQL($table)
758
    {
759
        $user = '';
760
761
        if (strpos($table, '.') !== false) {
762
            [$user, $table] = explode('.', $table);
763
            $user           = $this->quoteStringLiteral($user);
764
            $table          = $this->quoteStringLiteral($table);
765
        } else {
766
            $table = $this->quoteStringLiteral($table);
767
        }
768
769
        return sprintf(
770
            <<<'SQL'
771
SELECT con.*
772
FROM   SYS.SYSCONSTRAINT AS con
773
JOIN   SYS.SYSTAB AS tab ON con.table_object_id = tab.object_id
774
WHERE  tab.table_name = %s
775
AND    tab.creator = USER_ID(%s)
776
SQL
777
            ,
778
            $table,
779
            $user
780
        );
781
    }
782
783
    /**
784
     * {@inheritdoc}
785
     */
786
    public function getListTableForeignKeysSQL($table)
787
    {
788
        $user = '';
789
790
        if (strpos($table, '.') !== false) {
791
            [$user, $table] = explode('.', $table);
792
            $user           = $this->quoteStringLiteral($user);
793
            $table          = $this->quoteStringLiteral($table);
794
        } else {
795
            $table = $this->quoteStringLiteral($table);
796
        }
797
798
        return sprintf(
799
            <<<'SQL'
800
SELECT    fcol.column_name AS local_column,
801
          ptbl.table_name AS foreign_table,
802
          pcol.column_name AS foreign_column,
803
          idx.index_name,
804
          IF fk.nulls = 'N'
805
              THEN 1
806
              ELSE NULL
807
          ENDIF AS notnull,
808
          CASE ut.referential_action
809
              WHEN 'C' THEN 'CASCADE'
810
              WHEN 'D' THEN 'SET DEFAULT'
811
              WHEN 'N' THEN 'SET NULL'
812
              WHEN 'R' THEN 'RESTRICT'
813
              ELSE NULL
814
          END AS  on_update,
815
          CASE dt.referential_action
816
              WHEN 'C' THEN 'CASCADE'
817
              WHEN 'D' THEN 'SET DEFAULT'
818
              WHEN 'N' THEN 'SET NULL'
819
              WHEN 'R' THEN 'RESTRICT'
820
              ELSE NULL
821
          END AS on_delete,
822
          IF fk.check_on_commit = 'Y'
823
              THEN 1
824
              ELSE NULL
825
          ENDIF AS check_on_commit, -- check_on_commit flag
826
          IF ftbl.clustered_index_id = idx.index_id
827
              THEN 1
828
              ELSE NULL
829
          ENDIF AS 'clustered', -- clustered flag
830
          IF fk.match_type = 0
831
              THEN NULL
832
              ELSE fk.match_type
833
          ENDIF AS 'match', -- match option
834
          IF pidx.max_key_distance = 1
835
              THEN 1
836
              ELSE NULL
837
          ENDIF AS for_olap_workload -- for_olap_workload flag
838
FROM      SYS.SYSFKEY AS fk
839
JOIN      SYS.SYSIDX AS idx
840
ON        fk.foreign_table_id = idx.table_id
841
AND       fk.foreign_index_id = idx.index_id
842
JOIN      SYS.SYSPHYSIDX pidx
843
ON        idx.table_id = pidx.table_id
844
AND       idx.phys_index_id = pidx.phys_index_id
845
JOIN      SYS.SYSTAB AS ptbl
846
ON        fk.primary_table_id = ptbl.table_id
847
JOIN      SYS.SYSTAB AS ftbl
848
ON        fk.foreign_table_id = ftbl.table_id
849
JOIN      SYS.SYSIDXCOL AS idxcol
850
ON        idx.table_id = idxcol.table_id
851
AND       idx.index_id = idxcol.index_id
852
JOIN      SYS.SYSTABCOL AS pcol
853
ON        ptbl.table_id = pcol.table_id
854
AND       idxcol.primary_column_id = pcol.column_id
855
JOIN      SYS.SYSTABCOL AS fcol
856
ON        ftbl.table_id = fcol.table_id
857
AND       idxcol.column_id = fcol.column_id
858
LEFT JOIN SYS.SYSTRIGGER ut
859
ON        fk.foreign_table_id = ut.foreign_table_id
860
AND       fk.foreign_index_id = ut.foreign_key_id
861
AND       ut.event = 'C'
862
LEFT JOIN SYS.SYSTRIGGER dt
863
ON        fk.foreign_table_id = dt.foreign_table_id
864
AND       fk.foreign_index_id = dt.foreign_key_id
865
AND       dt.event = 'D'
866
WHERE     ftbl.table_name = %s
867
AND       ftbl.creator = USER_ID(%s)
868
ORDER BY  fk.foreign_index_id ASC, idxcol.sequence ASC
869
SQL
870
            ,
871
            $table,
872
            $user
873
        );
874
    }
875
876
    /**
877
     * {@inheritdoc}
878
     */
879
    public function getListTableIndexesSQL($table, $currentDatabase = null)
880
    {
881
        $user = '';
882
883
        if (strpos($table, '.') !== false) {
884
            [$user, $table] = explode('.', $table);
885
            $user           = $this->quoteStringLiteral($user);
886
            $table          = $this->quoteStringLiteral($table);
887
        } else {
888
            $table = $this->quoteStringLiteral($table);
889
        }
890
891
        return sprintf(
892
            <<<'SQL'
893
SELECT   idx.index_name AS key_name,
894
         IF idx.index_category = 1
895
             THEN 1
896
             ELSE 0
897
         ENDIF AS 'primary',
898
         col.column_name,
899
         IF idx."unique" IN(1, 2, 5)
900
             THEN 0
901
             ELSE 1
902
         ENDIF AS non_unique,
903
         IF tbl.clustered_index_id = idx.index_id
904
             THEN 1
905
             ELSE NULL
906
         ENDIF AS 'clustered', -- clustered flag
907
         IF idx."unique" = 5
908
             THEN 1
909
             ELSE NULL
910
         ENDIF AS with_nulls_not_distinct, -- with_nulls_not_distinct flag
911
         IF pidx.max_key_distance = 1
912
              THEN 1
913
              ELSE NULL
914
          ENDIF AS for_olap_workload -- for_olap_workload flag
915
FROM     SYS.SYSIDX AS idx
916
JOIN     SYS.SYSPHYSIDX pidx
917
ON       idx.table_id = pidx.table_id
918
AND      idx.phys_index_id = pidx.phys_index_id
919
JOIN     SYS.SYSIDXCOL AS idxcol
920
ON       idx.table_id = idxcol.table_id AND idx.index_id = idxcol.index_id
921
JOIN     SYS.SYSTABCOL AS col
922
ON       idxcol.table_id = col.table_id AND idxcol.column_id = col.column_id
923
JOIN     SYS.SYSTAB AS tbl
924
ON       idx.table_id = tbl.table_id
925
WHERE    tbl.table_name = %s
926
AND      tbl.creator = USER_ID(%s)
927
AND      idx.index_category != 2 -- exclude indexes implicitly created by foreign key constraints
928
ORDER BY idx.index_id ASC, idxcol.sequence ASC
929
SQL
930
            ,
931
            $table,
932
            $user
933
        );
934
    }
935
936
    /**
937
     * {@inheritdoc}
938
     */
939
    public function getListTablesSQL()
940
    {
941
        return "SELECT   tbl.table_name
942
                FROM     SYS.SYSTAB AS tbl
943
                JOIN     SYS.SYSUSER AS usr ON tbl.creator = usr.user_id
944
                JOIN     dbo.SYSOBJECTS AS obj ON tbl.object_id = obj.id
945
                WHERE    tbl.table_type IN(1, 3) -- 'BASE', 'GBL TEMP'
946
                AND      usr.user_name NOT IN('SYS', 'dbo', 'rs_systabgroup') -- exclude system users
947
                AND      obj.type = 'U' -- user created tables only
948
                ORDER BY tbl.table_name ASC";
949
    }
950
951
    /**
952
     * {@inheritdoc}
953
     *
954
     * @todo Where is this used? Which information should be retrieved?
955
     */
956
    public function getListUsersSQL()
957
    {
958
        return 'SELECT * FROM SYS.SYSUSER ORDER BY user_name ASC';
959
    }
960
961
    /**
962
     * {@inheritdoc}
963
     */
964
    public function getListViewsSQL($database)
965
    {
966
        return "SELECT   tbl.table_name, v.view_def
967
                FROM     SYS.SYSVIEW v
968
                JOIN     SYS.SYSTAB tbl ON v.view_object_id = tbl.object_id
969
                JOIN     SYS.SYSUSER usr ON tbl.creator = usr.user_id
970
                JOIN     dbo.SYSOBJECTS obj ON tbl.object_id = obj.id
971
                WHERE    usr.user_name NOT IN('SYS', 'dbo', 'rs_systabgroup') -- exclude system users
972
                ORDER BY tbl.table_name ASC";
973
    }
974
975
    /**
976
     * {@inheritdoc}
977
     */
978
    public function getLocateExpression($str, $substr, $startPos = false)
979
    {
980
        if ($startPos === false) {
981
            return 'LOCATE(' . $str . ', ' . $substr . ')';
982
        }
983
984
        return 'LOCATE(' . $str . ', ' . $substr . ', ' . $startPos . ')';
985
    }
986
987
    /**
988
     * {@inheritdoc}
989
     */
990
    public function getMaxIdentifierLength()
991
    {
992
        return 128;
993
    }
994
995
    /**
996
     * {@inheritdoc}
997
     */
998
    public function getMd5Expression($column)
999
    {
1000
        return 'HASH(' . $column . ", 'MD5')";
1001
    }
1002
1003
    /**
1004
     * {@inheritdoc}
1005
     */
1006
    public function getRegexpExpression()
1007
    {
1008
        return 'REGEXP';
1009
    }
1010
1011
    /**
1012
     * {@inheritdoc}
1013
     */
1014
    public function getName()
1015
    {
1016
        return 'sqlanywhere';
1017
    }
1018
1019
    /**
1020
     * Obtain DBMS specific SQL code portion needed to set a primary key
1021
     * declaration to be used in statements like ALTER TABLE.
1022
     *
1023
     * @param Index  $index Index definition
1024
     * @param string $name  Name of the primary key
1025
     *
1026
     * @return string DBMS specific SQL code portion needed to set a primary key
1027
     *
1028
     * @throws InvalidArgumentException If the given index is not a primary key.
1029
     */
1030
    public function getPrimaryKeyDeclarationSQL(Index $index, $name = null)
1031
    {
1032
        if (! $index->isPrimary()) {
1033
            throw new InvalidArgumentException(
1034
                'Can only create primary key declarations with getPrimaryKeyDeclarationSQL()'
1035
            );
1036
        }
1037
1038
        return $this->getTableConstraintDeclarationSQL($index, $name);
1039
    }
1040
1041
    /**
1042
     * {@inheritdoc}
1043
     */
1044
    public function getSetTransactionIsolationSQL($level)
1045
    {
1046
        return 'SET TEMPORARY OPTION isolation_level = ' . $this->_getTransactionIsolationLevelSQL($level);
1047
    }
1048
1049
    /**
1050
     * {@inheritdoc}
1051
     */
1052
    public function getSmallIntTypeDeclarationSQL(array $columnDef)
1053
    {
1054
        $columnDef['integer_type'] = 'SMALLINT';
1055
1056
        return $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
1057
    }
1058
1059
    /**
1060
     * Returns the SQL statement for starting an existing database.
1061
     *
1062
     * In SQL Anywhere you can start and stop databases on a
1063
     * database server instance.
1064
     * This is a required statement after having created a new database
1065
     * as it has to be explicitly started to be usable.
1066
     * SQL Anywhere does not automatically start a database after creation!
1067
     *
1068
     * @param string $database Name of the database to start.
1069
     *
1070
     * @return string
1071
     */
1072
    public function getStartDatabaseSQL($database)
1073
    {
1074
        $database = new Identifier($database);
1075
1076
        return "START DATABASE '" . $database->getName() . "' AUTOSTOP OFF";
1077
    }
1078
1079
    /**
1080
     * Returns the SQL statement for stopping a running database.
1081
     *
1082
     * In SQL Anywhere you can start and stop databases on a
1083
     * database server instance.
1084
     * This is a required statement before dropping an existing database
1085
     * as it has to be explicitly stopped before it can be dropped.
1086
     *
1087
     * @param string $database Name of the database to stop.
1088
     *
1089
     * @return string
1090
     */
1091
    public function getStopDatabaseSQL($database)
1092
    {
1093
        $database = new Identifier($database);
1094
1095
        return 'STOP DATABASE "' . $database->getName() . '" UNCONDITIONALLY';
1096
    }
1097
1098
    /**
1099
     * {@inheritdoc}
1100
     */
1101
    public function getSubstringExpression($value, $from, $length = null)
1102
    {
1103
        if ($length === null) {
1104
            return 'SUBSTRING(' . $value . ', ' . $from . ')';
1105
        }
1106
1107
        return 'SUBSTRING(' . $value . ', ' . $from . ', ' . $length . ')';
1108
    }
1109
1110
    /**
1111
     * {@inheritdoc}
1112
     */
1113
    public function getTemporaryTableSQL()
1114
    {
1115
        return 'GLOBAL TEMPORARY';
1116
    }
1117
1118
    /**
1119
     * {@inheritdoc}
1120
     */
1121
    public function getTimeFormatString()
1122
    {
1123
        return 'H:i:s.u';
1124
    }
1125
1126
    /**
1127
     * {@inheritdoc}
1128
     */
1129
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
1130
    {
1131
        return 'TIME';
1132
    }
1133
1134
    /**
1135
     * {@inheritdoc}
1136
     */
1137
    public function getTrimExpression($str, $pos = TrimMode::UNSPECIFIED, $char = false)
1138
    {
1139
        if (! $char) {
1140
            switch ($pos) {
1141
                case TrimMode::LEADING:
1142
                    return $this->getLtrimExpression($str);
1143
                case TrimMode::TRAILING:
1144
                    return $this->getRtrimExpression($str);
1145
                default:
1146
                    return 'TRIM(' . $str . ')';
1147
            }
1148
        }
1149
1150
        $pattern = "'%[^' + " . $char . " + ']%'";
1151
1152
        switch ($pos) {
1153
            case TrimMode::LEADING:
1154
                return 'SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))';
1155
            case TrimMode::TRAILING:
1156
                return 'REVERSE(SUBSTR(REVERSE(' . $str . '), PATINDEX(' . $pattern . ', REVERSE(' . $str . '))))';
1157
            default:
1158
                return 'REVERSE(SUBSTR(REVERSE(SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))), ' .
1159
                    'PATINDEX(' . $pattern . ', REVERSE(SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))))))';
1160
        }
1161
    }
1162
1163
    /**
1164
     * {@inheritdoc}
1165
     */
1166
    public function getTruncateTableSQL($tableName, $cascade = false)
1167
    {
1168
        $tableIdentifier = new Identifier($tableName);
1169
1170
        return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
1171
    }
1172
1173
    /**
1174
     * {@inheritdoc}
1175
     */
1176
    public function getUniqueConstraintDeclarationSQL($name, Index $index)
1177
    {
1178
        if ($index->isPrimary()) {
1179
            throw new InvalidArgumentException(
1180
                'Cannot create primary key constraint declarations with getUniqueConstraintDeclarationSQL().'
1181
            );
1182
        }
1183
1184
        if (! $index->isUnique()) {
1185
            throw new InvalidArgumentException(
1186
                'Can only create unique constraint declarations, no common index declarations with ' .
1187
                'getUniqueConstraintDeclarationSQL().'
1188
            );
1189
        }
1190
1191
        return $this->getTableConstraintDeclarationSQL($index, $name);
1192
    }
1193
1194
    /**
1195
     * {@inheritdoc}
1196
     */
1197
    public function getCreateSequenceSQL(Sequence $sequence)
1198
    {
1199
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
1200
            ' INCREMENT BY ' . $sequence->getAllocationSize() .
1201
            ' START WITH ' . $sequence->getInitialValue() .
1202
            ' MINVALUE ' . $sequence->getInitialValue();
1203
    }
1204
1205
    /**
1206
     * {@inheritdoc}
1207
     */
1208
    public function getAlterSequenceSQL(Sequence $sequence)
1209
    {
1210
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
1211
            ' INCREMENT BY ' . $sequence->getAllocationSize();
1212
    }
1213
1214
    /**
1215
     * {@inheritdoc}
1216
     */
1217
    public function getDropSequenceSQL($sequence)
1218
    {
1219
        if ($sequence instanceof Sequence) {
1220
            $sequence = $sequence->getQuotedName($this);
1221
        }
1222
1223
        return 'DROP SEQUENCE ' . $sequence;
1224
    }
1225
1226
    /**
1227
     * {@inheritdoc}
1228
     */
1229
    public function getListSequencesSQL($database)
1230
    {
1231
        return 'SELECT sequence_name, increment_by, start_with, min_value FROM SYS.SYSSEQUENCE';
1232
    }
1233
1234
    /**
1235
     * {@inheritdoc}
1236
     */
1237
    public function getSequenceNextValSQL($sequenceName)
1238
    {
1239
        return 'SELECT ' . $sequenceName . '.NEXTVAL';
1240
    }
1241
1242
    /**
1243
     * {@inheritdoc}
1244
     */
1245
    public function supportsSequences()
1246
    {
1247
        return true;
1248
    }
1249
1250
    /**
1251
     * {@inheritdoc}
1252
     */
1253
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
1254
    {
1255
        return 'TIMESTAMP WITH TIME ZONE';
1256
    }
1257
1258
    /**
1259
     * {@inheritdoc}
1260
     */
1261
    public function getVarcharDefaultLength()
1262
    {
1263
        return 1;
1264
    }
1265
1266
    /**
1267
     * {@inheritdoc}
1268
     */
1269
    public function getVarcharMaxLength()
1270
    {
1271
        return 32767;
1272
    }
1273
1274
    /**
1275
     * {@inheritdoc}
1276
     */
1277
    public function hasNativeGuidType()
1278
    {
1279
        return true;
1280
    }
1281
1282
    /**
1283
     * {@inheritdoc}
1284
     */
1285
    public function prefersIdentityColumns()
1286
    {
1287
        return true;
1288
    }
1289
1290
    /**
1291
     * {@inheritdoc}
1292
     */
1293
    public function supportsCommentOnStatement()
1294
    {
1295
        return true;
1296
    }
1297
1298
    /**
1299
     * {@inheritdoc}
1300
     */
1301
    public function supportsIdentityColumns()
1302
    {
1303
        return true;
1304
    }
1305
1306
    /**
1307
     * {@inheritdoc}
1308
     */
1309
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
1310
    {
1311
        $unsigned      = ! empty($columnDef['unsigned']) ? 'UNSIGNED ' : '';
1312
        $autoincrement = ! empty($columnDef['autoincrement']) ? ' IDENTITY' : '';
1313
1314
        return $unsigned . $columnDef['integer_type'] . $autoincrement;
1315
    }
1316
1317
    /**
1318
     * {@inheritdoc}
1319
     */
1320
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
1321
    {
1322
        $columnListSql = $this->getColumnDeclarationListSQL($columns);
1323
        $indexSql      = [];
1324
1325
        if (! empty($options['uniqueConstraints'])) {
1326
            foreach ((array) $options['uniqueConstraints'] as $name => $definition) {
1327
                $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
1328
            }
1329
        }
1330
1331
        if (! empty($options['indexes'])) {
1332
            /** @var Index $index */
1333
            foreach ((array) $options['indexes'] as $index) {
1334
                $indexSql[] = $this->getCreateIndexSQL($index, $tableName);
1335
            }
1336
        }
1337
1338
        if (! empty($options['primary'])) {
1339
            $flags = '';
1340
1341
            if (isset($options['primary_index']) && $options['primary_index']->hasFlag('clustered')) {
1342
                $flags = ' CLUSTERED ';
1343
            }
1344
1345
            $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values((array) $options['primary']))) . ')';
1346
        }
1347
1348
        if (! empty($options['foreignKeys'])) {
1349
            foreach ((array) $options['foreignKeys'] as $definition) {
1350
                $columnListSql .= ', ' . $this->getForeignKeyDeclarationSQL($definition);
1351
            }
1352
        }
1353
1354
        $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;
1355
        $check = $this->getCheckDeclarationSQL($columns);
1356
1357
        if (! empty($check)) {
1358
            $query .= ', ' . $check;
1359
        }
1360
1361
        $query .= ')';
1362
1363
        return array_merge([$query], $indexSql);
1364
    }
1365
1366
    /**
1367
     * {@inheritdoc}
1368
     */
1369
    protected function _getTransactionIsolationLevelSQL($level)
1370
    {
1371
        switch ($level) {
1372
            case TransactionIsolationLevel::READ_UNCOMMITTED:
1373
                return 0;
1374
            case TransactionIsolationLevel::READ_COMMITTED:
1375
                return 1;
1376
            case TransactionIsolationLevel::REPEATABLE_READ:
1377
                return 2;
1378
            case TransactionIsolationLevel::SERIALIZABLE:
1379
                return 3;
1380
            default:
1381
                throw new InvalidArgumentException('Invalid isolation level:' . $level);
1382
        }
1383
    }
1384
1385
    /**
1386
     * {@inheritdoc}
1387
     */
1388
    protected function doModifyLimitQuery($query, $limit, $offset)
1389
    {
1390
        $limitOffsetClause = $this->getTopClauseSQL($limit, $offset);
1391
1392
        if ($limitOffsetClause === '') {
1393
            return $query;
1394
        }
1395
1396
        if (! preg_match('/^\s*(SELECT\s+(DISTINCT\s+)?)(.*)/i', $query, $matches)) {
1397
            return $query;
1398
        }
1399
1400
        return $matches[1] . $limitOffsetClause . ' ' . $matches[3];
1401
    }
1402
1403
    private function getTopClauseSQL(?int $limit, ?int $offset) : string
1404
    {
1405
        if ($offset > 0) {
1406
            return sprintf('TOP %s START AT %d', $limit ?? 'ALL', $offset + 1);
1407
        }
1408
1409
        return $limit === null ? '' : 'TOP ' . $limit;
1410
    }
1411
1412
    /**
1413
     * Return the INDEX query section dealing with non-standard
1414
     * SQL Anywhere options.
1415
     *
1416
     * @param Index $index Index definition
1417
     *
1418
     * @return string
1419
     */
1420
    protected function getAdvancedIndexOptionsSQL(Index $index)
1421
    {
1422
        if ($index->hasFlag('with_nulls_distinct') && $index->hasFlag('with_nulls_not_distinct')) {
1423
            throw new UnexpectedValueException(
1424
                'An Index can either have a "with_nulls_distinct" or "with_nulls_not_distinct" flag but not both.'
1425
            );
1426
        }
1427
1428
        $sql = '';
1429
1430
        if (! $index->isPrimary() && $index->hasFlag('for_olap_workload')) {
1431
            $sql .= ' FOR OLAP WORKLOAD';
1432
        }
1433
1434
        if (! $index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_not_distinct')) {
1435
            return ' WITH NULLS NOT DISTINCT' . $sql;
1436
        }
1437
1438
        if (! $index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_distinct')) {
1439
            return ' WITH NULLS DISTINCT' . $sql;
1440
        }
1441
1442
        return $sql;
1443
    }
1444
1445
    /**
1446
     * {@inheritdoc}
1447
     */
1448
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
1449
    {
1450
        return $fixed
1451
            ? 'BINARY(' . ($length ?: $this->getBinaryDefaultLength()) . ')'
1452
            : 'VARBINARY(' . ($length ?: $this->getBinaryDefaultLength()) . ')';
1453
    }
1454
1455
    /**
1456
     * Returns the SQL snippet for creating a table constraint.
1457
     *
1458
     * @param Constraint  $constraint The table constraint to create the SQL snippet for.
1459
     * @param string|null $name       The table constraint name to use if any.
1460
     *
1461
     * @return string
1462
     *
1463
     * @throws InvalidArgumentException If the given table constraint type is not supported by this method.
1464
     */
1465
    protected function getTableConstraintDeclarationSQL(Constraint $constraint, $name = null)
1466
    {
1467
        if ($constraint instanceof ForeignKeyConstraint) {
1468
            return $this->getForeignKeyDeclarationSQL($constraint);
1469
        }
1470
1471
        if (! $constraint instanceof Index) {
1472
            throw new InvalidArgumentException('Unsupported constraint type: ' . get_class($constraint));
1473
        }
1474
1475
        if (! $constraint->isPrimary() && ! $constraint->isUnique()) {
1476
            throw new InvalidArgumentException(
1477
                'Can only create primary, unique or foreign key constraint declarations, no common index declarations ' .
1478
                'with getTableConstraintDeclarationSQL().'
1479
            );
1480
        }
1481
1482
        $constraintColumns = $constraint->getQuotedColumns($this);
1483
1484
        if (empty($constraintColumns)) {
1485
            throw new InvalidArgumentException("Incomplete definition. 'columns' required.");
1486
        }
1487
1488
        $sql   = '';
1489
        $flags = '';
1490
1491
        if (! empty($name)) {
1492
            $name = new Identifier($name);
1493
            $sql .= 'CONSTRAINT ' . $name->getQuotedName($this) . ' ';
1494
        }
1495
1496
        if ($constraint->hasFlag('clustered')) {
1497
            $flags = 'CLUSTERED ';
1498
        }
1499
1500
        if ($constraint->isPrimary()) {
1501
            return $sql . 'PRIMARY KEY ' . $flags . '(' . $this->getIndexFieldDeclarationListSQL($constraintColumns) . ')';
1502
        }
1503
1504
        return $sql . 'UNIQUE ' . $flags . '(' . $this->getIndexFieldDeclarationListSQL($constraintColumns) . ')';
1505
    }
1506
1507
    /**
1508
     * {@inheritdoc}
1509
     */
1510
    protected function getCreateIndexSQLFlags(Index $index)
1511
    {
1512
        $type = '';
1513
        if ($index->hasFlag('virtual')) {
1514
            $type .= 'VIRTUAL ';
1515
        }
1516
1517
        if ($index->isUnique()) {
1518
            $type .= 'UNIQUE ';
1519
        }
1520
1521
        if ($index->hasFlag('clustered')) {
1522
            $type .= 'CLUSTERED ';
1523
        }
1524
1525
        return $type;
1526
    }
1527
1528
    /**
1529
     * {@inheritdoc}
1530
     */
1531
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
1532
    {
1533
        return ['ALTER INDEX ' . $oldIndexName . ' ON ' . $tableName . ' RENAME TO ' . $index->getQuotedName($this)];
1534
    }
1535
1536
    /**
1537
     * {@inheritdoc}
1538
     */
1539
    protected function getReservedKeywordsClass()
1540
    {
1541
        return Keywords\SQLAnywhereKeywords::class;
1542
    }
1543
1544
    /**
1545
     * {@inheritdoc}
1546
     */
1547
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
1548
    {
1549
        return $fixed
1550
            ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(' . $this->getVarcharDefaultLength() . ')')
1551
            : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(' . $this->getVarcharDefaultLength() . ')');
1552
    }
1553
1554
    /**
1555
     * {@inheritdoc}
1556
     */
1557
    protected function initializeDoctrineTypeMappings()
1558
    {
1559
        $this->doctrineTypeMapping = [
1560
            'bigint'                   => 'bigint',
1561
            'binary'                   => 'binary',
1562
            'bit'                      => 'boolean',
1563
            'char'                     => 'string',
1564
            'decimal'                  => 'decimal',
1565
            'date'                     => 'date',
1566
            'datetime'                 => 'datetime',
1567
            'double'                   => 'float',
1568
            'float'                    => 'float',
1569
            'image'                    => 'blob',
1570
            'int'                      => 'integer',
1571
            'integer'                  => 'integer',
1572
            'long binary'              => 'blob',
1573
            'long nvarchar'            => 'text',
1574
            'long varbit'              => 'text',
1575
            'long varchar'             => 'text',
1576
            'money'                    => 'decimal',
1577
            'nchar'                    => 'string',
1578
            'ntext'                    => 'text',
1579
            'numeric'                  => 'decimal',
1580
            'nvarchar'                 => 'string',
1581
            'smalldatetime'            => 'datetime',
1582
            'smallint'                 => 'smallint',
1583
            'smallmoney'               => 'decimal',
1584
            'text'                     => 'text',
1585
            'time'                     => 'time',
1586
            'timestamp'                => 'datetime',
1587
            'timestamp with time zone' => 'datetime',
1588
            'tinyint'                  => 'smallint',
1589
            'uniqueidentifier'         => 'guid',
1590
            'uniqueidentifierstr'      => 'guid',
1591
            'unsigned bigint'          => 'bigint',
1592
            'unsigned int'             => 'integer',
1593
            'unsigned smallint'        => 'smallint',
1594
            'unsigned tinyint'         => 'smallint',
1595
            'varbinary'                => 'binary',
1596
            'varbit'                   => 'string',
1597
            'varchar'                  => 'string',
1598
            'xml'                      => 'text',
1599
        ];
1600
    }
1601
}
1602