Failed Conditions
Push — master ( 01143c...7811e4 )
by Sergei
21s queued 14s
created

SQLAnywhere16Platform   F

Complexity

Total Complexity 197

Size/Duplication

Total Lines 1316
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 197
eloc 434
dl 0
loc 1316
rs 2
c 0
b 0
f 0

89 Methods

Rating   Name   Duplication   Size   Complexity  
A appendLockHint() 0 14 4
A fixSchemaElementName() 0 9 2
A getCreateViewSQL() 0 3 1
A getDefaultTransactionIsolationLevel() 0 3 1
A getBooleanTypeDeclarationSQL() 0 5 3
A getTimeFormatString() 0 3 1
A getSubstringExpression() 0 7 2
A getIntegerTypeDeclarationSQL() 0 5 1
A getDateTimeTzTypeDeclarationSQL() 0 3 1
A getCreateIndexSQL() 0 3 1
A getLocateExpression() 0 7 2
B getForeignKeyBaseDeclarationSQL() 0 32 7
A getDateDiffExpression() 0 3 1
A getDateTimeTzFormatString() 0 3 1
A getAlterTableRenameColumnClause() 0 5 1
A _getCommonIntegerTypeDeclarationSQL() 0 6 3
A getName() 0 3 1
A getDateTimeFormatString() 0 3 1
A getTopClauseSQL() 0 7 3
A getDateArithmeticIntervalExpression() 0 9 2
A hasNativeGuidType() 0 3 1
A getListTableIndexesSQL() 0 54 2
A getRenameIndexSQL() 0 3 1
A getCurrentDatabaseExpression() 0 3 1
A getCurrentTimeSQL() 0 3 1
A getListTableForeignKeysSQL() 0 87 2
A getForeignKeyMatchClauseSQL() 0 17 5
A getAlterTableChangeColumnClause() 0 17 5
B getAdvancedForeignKeyOptionsSQL() 0 23 8
A getCreatePrimaryKeySQL() 0 7 2
A getClobTypeDeclarationSQL() 0 3 1
A getTemporaryTableSQL() 0 3 1
A getAlterTableAddColumnClause() 0 3 1
A getForUpdateSQL() 0 3 1
A getTruncateTableSQL() 0 5 1
A getBigIntTypeDeclarationSQL() 0 5 1
A getAlterSequenceSQL() 0 4 1
A getListTableConstraintsSQL() 0 23 2
A getSequenceNextValSQL() 0 3 1
A getListViewsSQL() 0 3 1
A getBlobTypeDeclarationSQL() 0 3 1
A initializeDoctrineTypeMappings() 0 42 1
A getDateTimeTypeDeclarationSQL() 0 3 1
A getForeignKeyReferentialActionSQL() 0 8 2
A supportsCommentOnStatement() 0 3 1
A getDropViewSQL() 0 3 1
A supportsSequences() 0 3 1
A _getTransactionIsolationLevelSQL() 0 17 5
B getTrimExpression() 0 33 7
A getMd5Expression() 0 3 1
A getConcatExpression() 0 3 1
A getRegexpExpression() 0 3 1
A getCreateDatabaseSQL() 0 5 1
A getDropIndexSQL() 0 27 6
A getCreateSequenceSQL() 0 6 1
A getListSequencesSQL() 0 3 1
A getListDatabasesSQL() 0 3 1
A getCreateIndexSQLFlags() 0 16 4
A getReservedKeywordsClass() 0 3 1
A getMaxIdentifierLength() 0 3 1
A doModifyLimitQuery() 0 13 3
A getStartDatabaseSQL() 0 5 1
A getCurrentDateSQL() 0 3 1
A getGuidTypeDeclarationSQL() 0 3 1
A getDropSequenceSQL() 0 7 2
A getDateTypeDeclarationSQL() 0 3 1
A prefersIdentityColumns() 0 3 1
A getTimeTypeDeclarationSQL() 0 3 1
A getAlterTableRemoveColumnClause() 0 3 1
A supportsIdentityColumns() 0 3 1
A getIndexDeclarationSQL() 0 4 1
F getAlterTableSQL() 0 91 16
B getAdvancedIndexOptionsSQL() 0 23 11
A getAlterTableClause() 0 3 1
A getListTablesSQL() 0 3 1
A getCreateConstraintSQL() 0 12 3
A getDropDatabaseSQL() 0 5 1
A getPrimaryKeyDeclarationSQL() 0 9 2
A getCurrentTimestampSQL() 0 3 1
B _getCreateTableSQL() 0 44 11
A getSetTransactionIsolationSQL() 0 3 1
A getListUsersSQL() 0 3 1
A getCreateTemporaryTableSnippetSQL() 0 3 1
A getCommentOnColumnSQL() 0 11 2
A getAlterTableRenameTableClause() 0 3 1
A getStopDatabaseSQL() 0 5 1
B getTableConstraintDeclarationSQL() 0 40 9
A getListTableColumnsSQL() 0 31 2
A getSmallIntTypeDeclarationSQL() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like SQLAnywhere16Platform often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SQLAnywhere16Platform, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Platforms;
6
7
use Doctrine\DBAL\LockMode;
8
use Doctrine\DBAL\Platforms\Exception\NotSupported;
9
use Doctrine\DBAL\Schema\Column;
10
use Doctrine\DBAL\Schema\ColumnDiff;
11
use Doctrine\DBAL\Schema\Constraint;
12
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
13
use Doctrine\DBAL\Schema\Identifier;
14
use Doctrine\DBAL\Schema\Index;
15
use Doctrine\DBAL\Schema\Sequence;
16
use Doctrine\DBAL\Schema\Table;
17
use Doctrine\DBAL\Schema\TableDiff;
18
use Doctrine\DBAL\TransactionIsolationLevel;
19
use InvalidArgumentException;
20
use UnexpectedValueException;
21
use function array_merge;
22
use function array_unique;
23
use function array_values;
24
use function assert;
25
use function count;
26
use function explode;
27
use function get_class;
28
use function implode;
29
use function in_array;
30
use function is_string;
31
use function preg_match;
32
use function sprintf;
33
use function strlen;
34
use function strpos;
35
use function strtoupper;
36
use function substr;
37
38
/**
39
 * Provides the behavior, features and SQL dialect of the SAP Sybase SQL Anywhere 16 database platform.
40
 */
41
class SQLAnywhere16Platform extends AbstractPlatform
42
{
43
    public const FOREIGN_KEY_MATCH_SIMPLE        = 1;
44
    public const FOREIGN_KEY_MATCH_FULL          = 2;
45
    public const FOREIGN_KEY_MATCH_SIMPLE_UNIQUE = 129;
46
    public const FOREIGN_KEY_MATCH_FULL_UNIQUE   = 130;
47
48
    public function appendLockHint(string $fromClause, ?int $lockMode) : string
49
    {
50
        switch (true) {
51
            case $lockMode === LockMode::NONE:
52
                return $fromClause . ' WITH (NOLOCK)';
53
54
            case $lockMode === LockMode::PESSIMISTIC_READ:
55
                return $fromClause . ' WITH (UPDLOCK)';
56
57
            case $lockMode === LockMode::PESSIMISTIC_WRITE:
58
                return $fromClause . ' WITH (XLOCK)';
59
60
            default:
61
                return $fromClause;
62
        }
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     *
68
     * SQL Anywhere supports a maximum length of 128 bytes for identifiers.
69
     */
70
    public function fixSchemaElementName(string $schemaElementName) : string
71
    {
72
        $maxIdentifierLength = $this->getMaxIdentifierLength();
73
74
        if (strlen($schemaElementName) > $maxIdentifierLength) {
75
            return substr($schemaElementName, 0, $maxIdentifierLength);
76
        }
77
78
        return $schemaElementName;
79
    }
80
81
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string
82
    {
83
        $query = '';
84
85
        if ($foreignKey->hasOption('match')) {
86
            $query = ' MATCH ' . $this->getForeignKeyMatchClauseSQL($foreignKey->getOption('match'));
87
        }
88
89
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
90
91
        if ($foreignKey->hasOption('check_on_commit') && (bool) $foreignKey->getOption('check_on_commit')) {
92
            $query .= ' CHECK ON COMMIT';
93
        }
94
95
        if ($foreignKey->hasOption('clustered') && (bool) $foreignKey->getOption('clustered')) {
96
            $query .= ' CLUSTERED';
97
        }
98
99
        if ($foreignKey->hasOption('for_olap_workload') && (bool) $foreignKey->getOption('for_olap_workload')) {
100
            $query .= ' FOR OLAP WORKLOAD';
101
        }
102
103
        return $query;
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function getAlterTableSQL(TableDiff $diff) : array
110
    {
111
        $sql          = [];
112
        $columnSql    = [];
113
        $commentsSQL  = [];
114
        $tableSql     = [];
115
        $alterClauses = [];
116
117
        foreach ($diff->addedColumns as $column) {
118
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
119
                continue;
120
            }
121
122
            $alterClauses[] = $this->getAlterTableAddColumnClause($column);
123
124
            $comment = $this->getColumnComment($column);
125
126
            if ($comment === null || $comment === '') {
127
                continue;
128
            }
129
130
            $commentsSQL[] = $this->getCommentOnColumnSQL(
131
                $diff->getName($this)->getQuotedName($this),
132
                $column->getQuotedName($this),
133
                $comment
134
            );
135
        }
136
137
        foreach ($diff->removedColumns as $column) {
138
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
139
                continue;
140
            }
141
142
            $alterClauses[] = $this->getAlterTableRemoveColumnClause($column);
143
        }
144
145
        foreach ($diff->changedColumns as $columnDiff) {
146
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
147
                continue;
148
            }
149
150
            $alterClause = $this->getAlterTableChangeColumnClause($columnDiff);
151
152
            if ($alterClause !== null) {
153
                $alterClauses[] = $alterClause;
154
            }
155
156
            if (! $columnDiff->hasChanged('comment')) {
157
                continue;
158
            }
159
160
            $column = $columnDiff->column;
161
162
            $commentsSQL[] = $this->getCommentOnColumnSQL(
163
                $diff->getName($this)->getQuotedName($this),
164
                $column->getQuotedName($this),
165
                $this->getColumnComment($column)
166
            );
167
        }
168
169
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
170
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
171
                continue;
172
            }
173
174
            $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' .
175
                $this->getAlterTableRenameColumnClause($oldColumnName, $column);
176
        }
177
178
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
179
            if (! empty($alterClauses)) {
180
                $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . implode(', ', $alterClauses);
181
            }
182
183
            $sql = array_merge($sql, $commentsSQL);
184
185
            $newName = $diff->getNewName();
186
187
            if ($newName !== null) {
188
                $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' .
189
                    $this->getAlterTableRenameTableClause($newName);
190
            }
191
192
            $sql = array_merge(
193
                $this->getPreAlterTableIndexForeignKeySQL($diff),
194
                $sql,
195
                $this->getPostAlterTableIndexForeignKeySQL($diff)
196
            );
197
        }
198
199
        return array_merge($sql, $tableSql, $columnSql);
200
    }
201
202
    /**
203
     * Returns the SQL clause for creating a column in a table alteration.
204
     *
205
     * @param Column $column The column to add.
206
     */
207
    protected function getAlterTableAddColumnClause(Column $column) : string
208
    {
209
        return 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
210
    }
211
212
    /**
213
     * Returns the SQL clause for altering a table.
214
     *
215
     * @param Identifier $tableName The quoted name of the table to alter.
216
     */
217
    protected function getAlterTableClause(Identifier $tableName) : string
218
    {
219
        return 'ALTER TABLE ' . $tableName->getQuotedName($this);
220
    }
221
222
    /**
223
     * Returns the SQL clause for dropping a column in a table alteration.
224
     *
225
     * @param Column $column The column to drop.
226
     */
227
    protected function getAlterTableRemoveColumnClause(Column $column) : string
228
    {
229
        return 'DROP ' . $column->getQuotedName($this);
230
    }
231
232
    /**
233
     * Returns the SQL clause for renaming a column in a table alteration.
234
     *
235
     * @param string $oldColumnName The quoted name of the column to rename.
236
     * @param Column $column        The column to rename to.
237
     */
238
    protected function getAlterTableRenameColumnClause(string $oldColumnName, Column $column) : string
239
    {
240
        $oldColumnName = new Identifier($oldColumnName);
241
242
        return 'RENAME ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this);
243
    }
244
245
    /**
246
     * Returns the SQL clause for renaming a table in a table alteration.
247
     *
248
     * @param Identifier $newTableName The quoted name of the table to rename to.
249
     */
250
    protected function getAlterTableRenameTableClause(Identifier $newTableName) : string
251
    {
252
        return 'RENAME ' . $newTableName->getQuotedName($this);
253
    }
254
255
    /**
256
     * Returns the SQL clause for altering a column in a table alteration.
257
     *
258
     * This method returns null in case that only the column comment has changed.
259
     * Changes in column comments have to be handled differently.
260
     *
261
     * @param ColumnDiff $columnDiff The diff of the column to alter.
262
     */
263
    protected function getAlterTableChangeColumnClause(ColumnDiff $columnDiff) : ?string
264
    {
265
        $column = $columnDiff->column;
266
267
        // Do not return alter clause if only comment has changed.
268
        if (! ($columnDiff->hasChanged('comment') && count($columnDiff->changedProperties) === 1)) {
269
            $columnAlterationClause = 'ALTER ' .
270
                $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
271
272
            if ($columnDiff->hasChanged('default') && $column->getDefault() === null) {
273
                $columnAlterationClause .= ', ALTER ' . $column->getQuotedName($this) . ' DROP DEFAULT';
274
            }
275
276
            return $columnAlterationClause;
277
        }
278
279
        return null;
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
286
    {
287
        $columnDef['integer_type'] = 'BIGINT';
288
289
        return $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295
    public function getBlobTypeDeclarationSQL(array $field) : string
296
    {
297
        return 'LONG BINARY';
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     *
303
     * BIT type columns require an explicit NULL declaration
304
     * in SQL Anywhere if they shall be nullable.
305
     * Otherwise by just omitting the NOT NULL clause,
306
     * SQL Anywhere will declare them NOT NULL nonetheless.
307
     */
308
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
309
    {
310
        $nullClause = isset($columnDef['notnull']) && (bool) $columnDef['notnull'] === false ? ' NULL' : '';
311
312
        return 'BIT' . $nullClause;
313
    }
314
315
    /**
316
     * {@inheritdoc}
317
     */
318
    public function getClobTypeDeclarationSQL(array $field) : string
319
    {
320
        return 'TEXT';
321
    }
322
323
    public function getCommentOnColumnSQL(string $tableName, string $columnName, ?string $comment) : string
324
    {
325
        $tableName  = new Identifier($tableName);
326
        $columnName = new Identifier($columnName);
327
        $comment    = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment);
328
329
        return sprintf(
330
            'COMMENT ON COLUMN %s.%s IS %s',
331
            $tableName->getQuotedName($this),
332
            $columnName->getQuotedName($this),
333
            $comment
334
        );
335
    }
336
337
    public function getConcatExpression(string ...$string) : string
338
    {
339
        return 'STRING(' . implode(', ', $string) . ')';
340
    }
341
342
    /**
343
     * {@inheritdoc}
344
     */
345
    public function getCreateConstraintSQL(Constraint $constraint, $table) : string
346
    {
347
        if ($constraint instanceof ForeignKeyConstraint) {
348
            return $this->getCreateForeignKeySQL($constraint, $table);
349
        }
350
351
        if ($table instanceof Table) {
352
            $table = $table->getQuotedName($this);
353
        }
354
355
        return 'ALTER TABLE ' . $table .
356
               ' ADD ' . $this->getTableConstraintDeclarationSQL($constraint, $constraint->getQuotedName($this));
357
    }
358
359
    public function getCreateDatabaseSQL(string $database) : string
360
    {
361
        $database = new Identifier($database);
362
363
        return "CREATE DATABASE '" . $database->getName() . "'";
364
    }
365
366
    /**
367
     * {@inheritdoc}
368
     *
369
     * Appends SQL Anywhere specific flags if given.
370
     */
371
    public function getCreateIndexSQL(Index $index, $table) : string
372
    {
373
        return parent::getCreateIndexSQL($index, $table) . $this->getAdvancedIndexOptionsSQL($index);
374
    }
375
376
    /**
377
     * {@inheritdoc}
378
     */
379
    public function getCreatePrimaryKeySQL(Index $index, $table) : string
380
    {
381
        if ($table instanceof Table) {
382
            $table = $table->getQuotedName($this);
383
        }
384
385
        return 'ALTER TABLE ' . $table . ' ADD ' . $this->getPrimaryKeyDeclarationSQL($index);
386
    }
387
388
    public function getCreateTemporaryTableSnippetSQL() : string
389
    {
390
        return 'CREATE ' . $this->getTemporaryTableSQL() . ' TABLE';
391
    }
392
393
    public function getCreateViewSQL(string $name, string $sql) : string
394
    {
395
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
396
    }
397
398
    public function getCurrentDateSQL() : string
399
    {
400
        return 'CURRENT DATE';
401
    }
402
403
    public function getCurrentTimeSQL() : string
404
    {
405
        return 'CURRENT TIME';
406
    }
407
408
    public function getCurrentTimestampSQL() : string
409
    {
410
        return 'CURRENT TIMESTAMP';
411
    }
412
413
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
414
    {
415
        $factorClause = '';
416
417
        if ($operator === '-') {
418
            $factorClause = '-1 * ';
419
        }
420
421
        return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')';
422
    }
423
424
    public function getDateDiffExpression(string $date1, string $date2) : string
425
    {
426
        return 'DATEDIFF(day, ' . $date2 . ', ' . $date1 . ')';
427
    }
428
429
    public function getDateTimeFormatString() : string
430
    {
431
        return 'Y-m-d H:i:s.u';
432
    }
433
434
    /**
435
     * {@inheritdoc}
436
     */
437
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
438
    {
439
        return 'DATETIME';
440
    }
441
442
    public function getDateTimeTzFormatString() : string
443
    {
444
        return 'Y-m-d H:i:s.uP';
445
    }
446
447
    /**
448
     * {@inheritdoc}
449
     */
450
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
451
    {
452
        return 'DATE';
453
    }
454
455
    public function getDefaultTransactionIsolationLevel() : int
456
    {
457
        return TransactionIsolationLevel::READ_UNCOMMITTED;
458
    }
459
460
    public function getDropDatabaseSQL(string $database) : string
461
    {
462
        $database = new Identifier($database);
463
464
        return "DROP DATABASE '" . $database->getName() . "'";
465
    }
466
467
    /**
468
     * {@inheritdoc}
469
     */
470
    public function getDropIndexSQL($index, $table = null) : string
471
    {
472
        if ($index instanceof Index) {
473
            $index = $index->getQuotedName($this);
474
        }
475
476
        if (! is_string($index)) {
477
            throw new InvalidArgumentException(
478
                sprintf('SQLAnywherePlatform::getDropIndexSQL() expects $index parameter to be a string or an instance of %s.', Index::class)
479
            );
480
        }
481
482
        if (! isset($table)) {
483
            return 'DROP INDEX ' . $index;
484
        }
485
486
        if ($table instanceof Table) {
487
            $table = $table->getQuotedName($this);
488
        }
489
490
        if (! is_string($table)) {
491
            throw new InvalidArgumentException(
492
                sprintf('SQLAnywherePlatform::getDropIndexSQL() expects $table parameter to be a string or an instance of %s.', Index::class)
493
            );
494
        }
495
496
        return 'DROP INDEX ' . $table . '.' . $index;
497
    }
498
499
    public function getDropViewSQL(string $name) : string
500
    {
501
        return 'DROP VIEW ' . $name;
502
    }
503
504
    public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) : string
505
    {
506
        $sql              = '';
507
        $foreignKeyName   = $foreignKey->getName();
508
        $localColumns     = $foreignKey->getQuotedLocalColumns($this);
509
        $foreignColumns   = $foreignKey->getQuotedForeignColumns($this);
510
        $foreignTableName = $foreignKey->getQuotedForeignTableName($this);
511
512
        if (! empty($foreignKeyName)) {
513
            $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' ';
514
        }
515
516
        if (empty($localColumns)) {
517
            throw new InvalidArgumentException('Incomplete definition. "local" required.');
518
        }
519
520
        if (empty($foreignColumns)) {
521
            throw new InvalidArgumentException('Incomplete definition. "foreign" required.');
522
        }
523
524
        if (empty($foreignTableName)) {
525
            throw new InvalidArgumentException('Incomplete definition. "foreignTable" required.');
526
        }
527
528
        if ($foreignKey->hasOption('notnull') && (bool) $foreignKey->getOption('notnull')) {
529
            $sql .= 'NOT NULL ';
530
        }
531
532
        return $sql .
533
            'FOREIGN KEY (' . $this->getColumnsFieldDeclarationListSQL($localColumns) . ') ' .
534
            'REFERENCES ' . $foreignKey->getQuotedForeignTableName($this) .
535
            ' (' . $this->getColumnsFieldDeclarationListSQL($foreignColumns) . ')';
536
    }
537
538
    /**
539
     * Returns foreign key MATCH clause for given type.
540
     *
541
     * @param int $type The foreign key match type
542
     *
543
     * @throws InvalidArgumentException If unknown match type given.
544
     */
545
    public function getForeignKeyMatchClauseSQL(int $type) : string
546
    {
547
        switch ($type) {
548
            case self::FOREIGN_KEY_MATCH_SIMPLE:
549
                return 'SIMPLE';
550
551
            case self::FOREIGN_KEY_MATCH_FULL:
552
                return 'FULL';
553
554
            case self::FOREIGN_KEY_MATCH_SIMPLE_UNIQUE:
555
                return 'UNIQUE SIMPLE';
556
557
            case self::FOREIGN_KEY_MATCH_FULL_UNIQUE:
558
                return 'UNIQUE FULL';
559
560
            default:
561
                throw new InvalidArgumentException(sprintf('Invalid foreign key match type "%s".', $type));
562
        }
563
    }
564
565
    public function getForeignKeyReferentialActionSQL(string $action) : string
566
    {
567
        // NO ACTION is not supported, therefore falling back to RESTRICT.
568
        if (strtoupper($action) === 'NO ACTION') {
569
            return 'RESTRICT';
570
        }
571
572
        return parent::getForeignKeyReferentialActionSQL($action);
573
    }
574
575
    public function getForUpdateSQL() : string
576
    {
577
        return '';
578
    }
579
580
    /**
581
     * {@inheritdoc}
582
     */
583
    public function getGuidTypeDeclarationSQL(array $column) : string
584
    {
585
        return 'UNIQUEIDENTIFIER';
586
    }
587
588
    public function getIndexDeclarationSQL(string $name, Index $index) : string
589
    {
590
        // Index declaration in statements like CREATE TABLE is not supported.
591
        throw NotSupported::new(__METHOD__);
592
    }
593
594
    /**
595
     * {@inheritdoc}
596
     */
597
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
598
    {
599
        $columnDef['integer_type'] = 'INT';
600
601
        return $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
602
    }
603
604
    public function getListDatabasesSQL() : string
605
    {
606
        return 'SELECT db_name(number) AS name FROM sa_db_list()';
607
    }
608
609
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
610
    {
611
        $user = 'USER_NAME()';
612
613
        if (strpos($table, '.') !== false) {
614
            [$user, $table] = explode('.', $table);
615
            $user           = $this->quoteStringLiteral($user);
616
        }
617
618
        return sprintf(
619
            <<<'SQL'
620
SELECT    col.column_name,
621
          COALESCE(def.user_type_name, def.domain_name) AS 'type',
622
          def.declared_width AS 'length',
623
          def.scale,
624
          CHARINDEX('unsigned', def.domain_name) AS 'unsigned',
625
          IF col.nulls = 'Y' THEN 0 ELSE 1 ENDIF AS 'notnull',
626
          col."default",
627
          def.is_autoincrement AS 'autoincrement',
628
          rem.remarks AS 'comment'
629
FROM      sa_describe_query('SELECT * FROM "%s"') AS def
630
JOIN      SYS.SYSTABCOL AS col
631
ON        col.table_id = def.base_table_id AND col.column_id = def.base_column_id
632
LEFT JOIN SYS.SYSREMARK AS rem
633
ON        col.object_id = rem.object_id
634
WHERE     def.base_owner_name = %s
635
ORDER BY  def.base_column_id ASC
636
SQL
637
            ,
638
            $table,
639
            $user
640
        );
641
    }
642
643
    /**
644
     * {@inheritdoc}
645
     *
646
     * @todo Where is this used? Which information should be retrieved?
647
     */
648
    public function getListTableConstraintsSQL(string $table) : string
649
    {
650
        $user = '';
651
652
        if (strpos($table, '.') !== false) {
653
            [$user, $table] = explode('.', $table);
654
            $user           = $this->quoteStringLiteral($user);
655
            $table          = $this->quoteStringLiteral($table);
656
        } else {
657
            $table = $this->quoteStringLiteral($table);
658
        }
659
660
        return sprintf(
661
            <<<'SQL'
662
SELECT con.*
663
FROM   SYS.SYSCONSTRAINT AS con
664
JOIN   SYS.SYSTAB AS tab ON con.table_object_id = tab.object_id
665
WHERE  tab.table_name = %s
666
AND    tab.creator = USER_ID(%s)
667
SQL
668
            ,
669
            $table,
670
            $user
671
        );
672
    }
673
674
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
675
    {
676
        $user = '';
677
678
        if (strpos($table, '.') !== false) {
679
            [$user, $table] = explode('.', $table);
680
            $user           = $this->quoteStringLiteral($user);
681
            $table          = $this->quoteStringLiteral($table);
682
        } else {
683
            $table = $this->quoteStringLiteral($table);
684
        }
685
686
        return sprintf(
687
            <<<'SQL'
688
SELECT    fcol.column_name AS local_column,
689
          ptbl.table_name AS foreign_table,
690
          pcol.column_name AS foreign_column,
691
          idx.index_name,
692
          IF fk.nulls = 'N'
693
              THEN 1
694
              ELSE NULL
695
          ENDIF AS notnull,
696
          CASE ut.referential_action
697
              WHEN 'C' THEN 'CASCADE'
698
              WHEN 'D' THEN 'SET DEFAULT'
699
              WHEN 'N' THEN 'SET NULL'
700
              WHEN 'R' THEN 'RESTRICT'
701
              ELSE NULL
702
          END AS  on_update,
703
          CASE dt.referential_action
704
              WHEN 'C' THEN 'CASCADE'
705
              WHEN 'D' THEN 'SET DEFAULT'
706
              WHEN 'N' THEN 'SET NULL'
707
              WHEN 'R' THEN 'RESTRICT'
708
              ELSE NULL
709
          END AS on_delete,
710
          IF fk.check_on_commit = 'Y'
711
              THEN 1
712
              ELSE NULL
713
          ENDIF AS check_on_commit, -- check_on_commit flag
714
          IF ftbl.clustered_index_id = idx.index_id
715
              THEN 1
716
              ELSE NULL
717
          ENDIF AS 'clustered', -- clustered flag
718
          IF fk.match_type = 0
719
              THEN NULL
720
              ELSE fk.match_type
721
          ENDIF AS 'match', -- match option
722
          IF pidx.max_key_distance = 1
723
              THEN 1
724
              ELSE NULL
725
          ENDIF AS for_olap_workload -- for_olap_workload flag
726
FROM      SYS.SYSFKEY AS fk
727
JOIN      SYS.SYSIDX AS idx
728
ON        fk.foreign_table_id = idx.table_id
729
AND       fk.foreign_index_id = idx.index_id
730
JOIN      SYS.SYSPHYSIDX pidx
731
ON        idx.table_id = pidx.table_id
732
AND       idx.phys_index_id = pidx.phys_index_id
733
JOIN      SYS.SYSTAB AS ptbl
734
ON        fk.primary_table_id = ptbl.table_id
735
JOIN      SYS.SYSTAB AS ftbl
736
ON        fk.foreign_table_id = ftbl.table_id
737
JOIN      SYS.SYSIDXCOL AS idxcol
738
ON        idx.table_id = idxcol.table_id
739
AND       idx.index_id = idxcol.index_id
740
JOIN      SYS.SYSTABCOL AS pcol
741
ON        ptbl.table_id = pcol.table_id
742
AND       idxcol.primary_column_id = pcol.column_id
743
JOIN      SYS.SYSTABCOL AS fcol
744
ON        ftbl.table_id = fcol.table_id
745
AND       idxcol.column_id = fcol.column_id
746
LEFT JOIN SYS.SYSTRIGGER ut
747
ON        fk.foreign_table_id = ut.foreign_table_id
748
AND       fk.foreign_index_id = ut.foreign_key_id
749
AND       ut.event = 'C'
750
LEFT JOIN SYS.SYSTRIGGER dt
751
ON        fk.foreign_table_id = dt.foreign_table_id
752
AND       fk.foreign_index_id = dt.foreign_key_id
753
AND       dt.event = 'D'
754
WHERE     ftbl.table_name = %s
755
AND       ftbl.creator = USER_ID(%s)
756
ORDER BY  fk.foreign_index_id ASC, idxcol.sequence ASC
757
SQL
758
            ,
759
            $table,
760
            $user
761
        );
762
    }
763
764
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
765
    {
766
        $user = '';
767
768
        if (strpos($table, '.') !== false) {
769
            [$user, $table] = explode('.', $table);
770
            $user           = $this->quoteStringLiteral($user);
771
            $table          = $this->quoteStringLiteral($table);
772
        } else {
773
            $table = $this->quoteStringLiteral($table);
774
        }
775
776
        return sprintf(
777
            <<<'SQL'
778
SELECT   idx.index_name AS key_name,
779
         IF idx.index_category = 1
780
             THEN 1
781
             ELSE 0
782
         ENDIF AS 'primary',
783
         col.column_name,
784
         IF idx."unique" IN(1, 2, 5)
785
             THEN 0
786
             ELSE 1
787
         ENDIF AS non_unique,
788
         IF tbl.clustered_index_id = idx.index_id
789
             THEN 1
790
             ELSE NULL
791
         ENDIF AS 'clustered', -- clustered flag
792
         IF idx."unique" = 5
793
             THEN 1
794
             ELSE NULL
795
         ENDIF AS with_nulls_not_distinct, -- with_nulls_not_distinct flag
796
         IF pidx.max_key_distance = 1
797
              THEN 1
798
              ELSE NULL
799
          ENDIF AS for_olap_workload -- for_olap_workload flag
800
FROM     SYS.SYSIDX AS idx
801
JOIN     SYS.SYSPHYSIDX pidx
802
ON       idx.table_id = pidx.table_id
803
AND      idx.phys_index_id = pidx.phys_index_id
804
JOIN     SYS.SYSIDXCOL AS idxcol
805
ON       idx.table_id = idxcol.table_id AND idx.index_id = idxcol.index_id
806
JOIN     SYS.SYSTABCOL AS col
807
ON       idxcol.table_id = col.table_id AND idxcol.column_id = col.column_id
808
JOIN     SYS.SYSTAB AS tbl
809
ON       idx.table_id = tbl.table_id
810
WHERE    tbl.table_name = %s
811
AND      tbl.creator = USER_ID(%s)
812
AND      idx.index_category != 2 -- exclude indexes implicitly created by foreign key constraints
813
ORDER BY idx.index_id ASC, idxcol.sequence ASC
814
SQL
815
            ,
816
            $table,
817
            $user
818
        );
819
    }
820
821
    public function getListTablesSQL() : string
822
    {
823
        return "SELECT   tbl.table_name
824
                FROM     SYS.SYSTAB AS tbl
825
                JOIN     SYS.SYSUSER AS usr ON tbl.creator = usr.user_id
826
                JOIN     dbo.SYSOBJECTS AS obj ON tbl.object_id = obj.id
827
                WHERE    tbl.table_type IN(1, 3) -- 'BASE', 'GBL TEMP'
828
                AND      usr.user_name NOT IN('SYS', 'dbo', 'rs_systabgroup') -- exclude system users
829
                AND      obj.type = 'U' -- user created tables only
830
                ORDER BY tbl.table_name ASC";
831
    }
832
833
    /**
834
     * {@inheritdoc}
835
     *
836
     * @todo Where is this used? Which information should be retrieved?
837
     */
838
    public function getListUsersSQL() : string
839
    {
840
        return 'SELECT * FROM SYS.SYSUSER ORDER BY user_name ASC';
841
    }
842
843
    public function getListViewsSQL(string $database) : string
844
    {
845
        return "SELECT   tbl.table_name, v.view_def
846
                FROM     SYS.SYSVIEW v
847
                JOIN     SYS.SYSTAB tbl ON v.view_object_id = tbl.object_id
848
                JOIN     SYS.SYSUSER usr ON tbl.creator = usr.user_id
849
                JOIN     dbo.SYSOBJECTS obj ON tbl.object_id = obj.id
850
                WHERE    usr.user_name NOT IN('SYS', 'dbo', 'rs_systabgroup') -- exclude system users
851
                ORDER BY tbl.table_name ASC";
852
    }
853
854
    public function getLocateExpression(string $string, string $substring, ?string $start = null) : string
855
    {
856
        if ($start === null) {
857
            return sprintf('LOCATE(%s, %s)', $string, $substring);
858
        }
859
860
        return sprintf('LOCATE(%s, %s, %s)', $string, $substring, $start);
861
    }
862
863
    public function getMaxIdentifierLength() : int
864
    {
865
        return 128;
866
    }
867
868
    public function getMd5Expression(string $string) : string
869
    {
870
        return 'HASH(' . $string . ", 'MD5')";
871
    }
872
873
    public function getRegexpExpression() : string
874
    {
875
        return 'REGEXP';
876
    }
877
878
    public function getName() : string
879
    {
880
        return 'sqlanywhere';
881
    }
882
883
    /**
884
     * Obtain DBMS specific SQL code portion needed to set a primary key
885
     * declaration to be used in statements like ALTER TABLE.
886
     *
887
     * @param Index  $index Index definition
888
     * @param string $name  Name of the primary key
889
     *
890
     * @return string DBMS specific SQL code portion needed to set a primary key
891
     *
892
     * @throws InvalidArgumentException If the given index is not a primary key.
893
     */
894
    public function getPrimaryKeyDeclarationSQL(Index $index, ?string $name = null) : string
895
    {
896
        if (! $index->isPrimary()) {
897
            throw new InvalidArgumentException(
898
                'Can only create primary key declarations with getPrimaryKeyDeclarationSQL()'
899
            );
900
        }
901
902
        return $this->getTableConstraintDeclarationSQL($index, $name);
903
    }
904
905
    public function getSetTransactionIsolationSQL(int $level) : string
906
    {
907
        return 'SET TEMPORARY OPTION isolation_level = ' . $this->_getTransactionIsolationLevelSQL($level);
908
    }
909
910
    /**
911
     * {@inheritdoc}
912
     */
913
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
914
    {
915
        $columnDef['integer_type'] = 'SMALLINT';
916
917
        return $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
918
    }
919
920
    /**
921
     * Returns the SQL statement for starting an existing database.
922
     *
923
     * In SQL Anywhere you can start and stop databases on a
924
     * database server instance.
925
     * This is a required statement after having created a new database
926
     * as it has to be explicitly started to be usable.
927
     * SQL Anywhere does not automatically start a database after creation!
928
     *
929
     * @param string $database Name of the database to start.
930
     */
931
    public function getStartDatabaseSQL(string $database) : string
932
    {
933
        $database = new Identifier($database);
934
935
        return "START DATABASE '" . $database->getName() . "' AUTOSTOP OFF";
936
    }
937
938
    /**
939
     * Returns the SQL statement for stopping a running database.
940
     *
941
     * In SQL Anywhere you can start and stop databases on a
942
     * database server instance.
943
     * This is a required statement before dropping an existing database
944
     * as it has to be explicitly stopped before it can be dropped.
945
     *
946
     * @param string $database Name of the database to stop.
947
     */
948
    public function getStopDatabaseSQL(string $database) : string
949
    {
950
        $database = new Identifier($database);
951
952
        return 'STOP DATABASE "' . $database->getName() . '" UNCONDITIONALLY';
953
    }
954
955
    public function getSubstringExpression(string $string, string $start, ?string $length = null) : string
956
    {
957
        if ($length === null) {
958
            return sprintf('SUBSTRING(%s, %s)', $string, $start);
959
        }
960
961
        return sprintf('SUBSTRING(%s, %s, %s)', $string, $start, $length);
962
    }
963
964
    public function getTemporaryTableSQL() : string
965
    {
966
        return 'GLOBAL TEMPORARY';
967
    }
968
969
    public function getTimeFormatString() : string
970
    {
971
        return 'H:i:s.u';
972
    }
973
974
    /**
975
     * {@inheritdoc}
976
     */
977
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
978
    {
979
        return 'TIME';
980
    }
981
982
    public function getTrimExpression(string $str, int $mode = TrimMode::UNSPECIFIED, ?string $char = null) : string
983
    {
984
        if (! in_array($mode, [TrimMode::UNSPECIFIED, TrimMode::LEADING, TrimMode::TRAILING, TrimMode::BOTH], true)) {
985
            throw new InvalidArgumentException(
986
                sprintf('The value of $mode is expected to be one of the TrimMode constants, %d given', $mode)
987
            );
988
        }
989
990
        if ($char === null) {
991
            switch ($mode) {
992
                case TrimMode::LEADING:
993
                    return $this->getLtrimExpression($str);
994
995
                case TrimMode::TRAILING:
996
                    return $this->getRtrimExpression($str);
997
998
                default:
999
                    return 'TRIM(' . $str . ')';
1000
            }
1001
        }
1002
1003
        $pattern = "'%[^' + " . $char . " + ']%'";
1004
1005
        switch ($mode) {
1006
            case TrimMode::LEADING:
1007
                return 'SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))';
1008
1009
            case TrimMode::TRAILING:
1010
                return 'REVERSE(SUBSTR(REVERSE(' . $str . '), PATINDEX(' . $pattern . ', REVERSE(' . $str . '))))';
1011
1012
            default:
1013
                return 'REVERSE(SUBSTR(REVERSE(SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))), ' .
1014
                    'PATINDEX(' . $pattern . ', REVERSE(SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))))))';
1015
        }
1016
    }
1017
1018
    public function getCurrentDatabaseExpression() : string
1019
    {
1020
        return 'DB_NAME()';
1021
    }
1022
1023
    public function getTruncateTableSQL(string $tableName, bool $cascade = false) : string
1024
    {
1025
        $tableIdentifier = new Identifier($tableName);
1026
1027
        return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
1028
    }
1029
1030
    public function getCreateSequenceSQL(Sequence $sequence) : string
1031
    {
1032
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
1033
            ' INCREMENT BY ' . $sequence->getAllocationSize() .
1034
            ' START WITH ' . $sequence->getInitialValue() .
1035
            ' MINVALUE ' . $sequence->getInitialValue();
1036
    }
1037
1038
    public function getAlterSequenceSQL(Sequence $sequence) : string
1039
    {
1040
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
1041
            ' INCREMENT BY ' . $sequence->getAllocationSize();
1042
    }
1043
1044
    /**
1045
     * {@inheritdoc}
1046
     */
1047
    public function getDropSequenceSQL($sequence) : string
1048
    {
1049
        if ($sequence instanceof Sequence) {
1050
            $sequence = $sequence->getQuotedName($this);
1051
        }
1052
1053
        return 'DROP SEQUENCE ' . $sequence;
1054
    }
1055
1056
    public function getListSequencesSQL(string $database) : string
1057
    {
1058
        return 'SELECT sequence_name, increment_by, start_with, min_value FROM SYS.SYSSEQUENCE';
1059
    }
1060
1061
    public function getSequenceNextValSQL(string $sequenceName) : string
1062
    {
1063
        return 'SELECT ' . $sequenceName . '.NEXTVAL';
1064
    }
1065
1066
    public function supportsSequences() : bool
1067
    {
1068
        return true;
1069
    }
1070
1071
    /**
1072
     * {@inheritdoc}
1073
     */
1074
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) : string
1075
    {
1076
        return 'TIMESTAMP WITH TIME ZONE';
1077
    }
1078
1079
    public function hasNativeGuidType() : bool
1080
    {
1081
        return true;
1082
    }
1083
1084
    public function prefersIdentityColumns() : bool
1085
    {
1086
        return true;
1087
    }
1088
1089
    public function supportsCommentOnStatement() : bool
1090
    {
1091
        return true;
1092
    }
1093
1094
    public function supportsIdentityColumns() : bool
1095
    {
1096
        return true;
1097
    }
1098
1099
    /**
1100
     * {@inheritdoc}
1101
     */
1102
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
1103
    {
1104
        $unsigned      = ! empty($columnDef['unsigned']) ? 'UNSIGNED ' : '';
1105
        $autoincrement = ! empty($columnDef['autoincrement']) ? ' IDENTITY' : '';
1106
1107
        return $unsigned . $columnDef['integer_type'] . $autoincrement;
1108
    }
1109
1110
    /**
1111
     * {@inheritdoc}
1112
     */
1113
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
1114
    {
1115
        $columnListSql = $this->getColumnDeclarationListSQL($columns);
1116
        $indexSql      = [];
1117
1118
        if (! empty($options['uniqueConstraints'])) {
1119
            foreach ((array) $options['uniqueConstraints'] as $name => $definition) {
1120
                $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
1121
            }
1122
        }
1123
1124
        if (! empty($options['indexes'])) {
1125
            foreach ((array) $options['indexes'] as $index) {
1126
                assert($index instanceof Index);
1127
                $indexSql[] = $this->getCreateIndexSQL($index, $tableName);
1128
            }
1129
        }
1130
1131
        if (! empty($options['primary'])) {
1132
            $flags = '';
1133
1134
            if (isset($options['primary_index']) && $options['primary_index']->hasFlag('clustered')) {
1135
                $flags = ' CLUSTERED ';
1136
            }
1137
1138
            $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values((array) $options['primary']))) . ')';
1139
        }
1140
1141
        if (! empty($options['foreignKeys'])) {
1142
            foreach ((array) $options['foreignKeys'] as $definition) {
1143
                $columnListSql .= ', ' . $this->getForeignKeyDeclarationSQL($definition);
1144
            }
1145
        }
1146
1147
        $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;
1148
        $check = $this->getCheckDeclarationSQL($columns);
1149
1150
        if (! empty($check)) {
1151
            $query .= ', ' . $check;
1152
        }
1153
1154
        $query .= ')';
1155
1156
        return array_merge([$query], $indexSql);
1157
    }
1158
1159
    protected function _getTransactionIsolationLevelSQL(int $level) : string
1160
    {
1161
        switch ($level) {
1162
            case TransactionIsolationLevel::READ_UNCOMMITTED:
1163
                return '0';
1164
1165
            case TransactionIsolationLevel::READ_COMMITTED:
1166
                return '1';
1167
1168
            case TransactionIsolationLevel::REPEATABLE_READ:
1169
                return '2';
1170
1171
            case TransactionIsolationLevel::SERIALIZABLE:
1172
                return '3';
1173
1174
            default:
1175
                throw new InvalidArgumentException(sprintf('Invalid isolation level %d.', $level));
1176
        }
1177
    }
1178
1179
    protected function doModifyLimitQuery(string $query, ?int $limit, int $offset) : string
1180
    {
1181
        $limitOffsetClause = $this->getTopClauseSQL($limit, $offset);
1182
1183
        if ($limitOffsetClause === '') {
1184
            return $query;
1185
        }
1186
1187
        if (! preg_match('/^\s*(SELECT\s+(DISTINCT\s+)?)(.*)/i', $query, $matches)) {
1188
            return $query;
1189
        }
1190
1191
        return $matches[1] . $limitOffsetClause . ' ' . $matches[3];
1192
    }
1193
1194
    private function getTopClauseSQL(?int $limit, ?int $offset) : string
1195
    {
1196
        if ($offset > 0) {
1197
            return sprintf('TOP %s START AT %d', $limit ?? 'ALL', $offset + 1);
1198
        }
1199
1200
        return $limit === null ? '' : 'TOP ' . $limit;
1201
    }
1202
1203
    /**
1204
     * Return the INDEX query section dealing with non-standard
1205
     * SQL Anywhere options.
1206
     *
1207
     * @param Index $index Index definition
1208
     */
1209
    protected function getAdvancedIndexOptionsSQL(Index $index) : string
1210
    {
1211
        if ($index->hasFlag('with_nulls_distinct') && $index->hasFlag('with_nulls_not_distinct')) {
1212
            throw new UnexpectedValueException(
1213
                'An Index can either have a "with_nulls_distinct" or "with_nulls_not_distinct" flag but not both.'
1214
            );
1215
        }
1216
1217
        $sql = '';
1218
1219
        if (! $index->isPrimary() && $index->hasFlag('for_olap_workload')) {
1220
            $sql .= ' FOR OLAP WORKLOAD';
1221
        }
1222
1223
        if (! $index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_not_distinct')) {
1224
            return ' WITH NULLS NOT DISTINCT' . $sql;
1225
        }
1226
1227
        if (! $index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_distinct')) {
1228
            return ' WITH NULLS DISTINCT' . $sql;
1229
        }
1230
1231
        return $sql;
1232
    }
1233
1234
    /**
1235
     * Returns the SQL snippet for creating a table constraint.
1236
     *
1237
     * @param Constraint  $constraint The table constraint to create the SQL snippet for.
1238
     * @param string|null $name       The table constraint name to use if any.
1239
     *
1240
     * @throws InvalidArgumentException If the given table constraint type is not supported by this method.
1241
     */
1242
    protected function getTableConstraintDeclarationSQL(Constraint $constraint, ?string $name = null) : string
1243
    {
1244
        if ($constraint instanceof ForeignKeyConstraint) {
1245
            return $this->getForeignKeyDeclarationSQL($constraint);
1246
        }
1247
1248
        if (! $constraint instanceof Index) {
1249
            throw new InvalidArgumentException(sprintf('Unsupported constraint type %s.', get_class($constraint)));
1250
        }
1251
1252
        if (! $constraint->isPrimary() && ! $constraint->isUnique()) {
1253
            throw new InvalidArgumentException(
1254
                'Can only create primary, unique or foreign key constraint declarations, no common index declarations ' .
1255
                'with getTableConstraintDeclarationSQL().'
1256
            );
1257
        }
1258
1259
        $constraintColumns = $constraint->getQuotedColumns($this);
1260
1261
        if (empty($constraintColumns)) {
1262
            throw new InvalidArgumentException('Incomplete definition. "columns" required.');
1263
        }
1264
1265
        $sql   = '';
1266
        $flags = '';
1267
1268
        if (! empty($name)) {
1269
            $name = new Identifier($name);
1270
            $sql .= 'CONSTRAINT ' . $name->getQuotedName($this) . ' ';
1271
        }
1272
1273
        if ($constraint->hasFlag('clustered')) {
1274
            $flags = 'CLUSTERED ';
1275
        }
1276
1277
        if ($constraint->isPrimary()) {
1278
            return $sql . 'PRIMARY KEY ' . $flags . '(' . $this->getColumnsFieldDeclarationListSQL($constraintColumns) . ')';
1279
        }
1280
1281
        return $sql . 'UNIQUE ' . $flags . '(' . $this->getColumnsFieldDeclarationListSQL($constraintColumns) . ')';
1282
    }
1283
1284
    protected function getCreateIndexSQLFlags(Index $index) : string
1285
    {
1286
        $type = '';
1287
        if ($index->hasFlag('virtual')) {
1288
            $type .= 'VIRTUAL ';
1289
        }
1290
1291
        if ($index->isUnique()) {
1292
            $type .= 'UNIQUE ';
1293
        }
1294
1295
        if ($index->hasFlag('clustered')) {
1296
            $type .= 'CLUSTERED ';
1297
        }
1298
1299
        return $type;
1300
    }
1301
1302
    /**
1303
     * {@inheritdoc}
1304
     */
1305
    protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName) : array
1306
    {
1307
        return ['ALTER INDEX ' . $oldIndexName . ' ON ' . $tableName . ' RENAME TO ' . $index->getQuotedName($this)];
1308
    }
1309
1310
    protected function getReservedKeywordsClass() : string
1311
    {
1312
        return Keywords\SQLAnywhere16Keywords::class;
1313
    }
1314
1315
    protected function initializeDoctrineTypeMappings() : void
1316
    {
1317
        $this->doctrineTypeMapping = [
1318
            'bigint'                   => 'bigint',
1319
            'binary'                   => 'binary',
1320
            'bit'                      => 'boolean',
1321
            'char'                     => 'string',
1322
            'decimal'                  => 'decimal',
1323
            'date'                     => 'date',
1324
            'datetime'                 => 'datetime',
1325
            'double'                   => 'float',
1326
            'float'                    => 'float',
1327
            'image'                    => 'blob',
1328
            'int'                      => 'integer',
1329
            'integer'                  => 'integer',
1330
            'long binary'              => 'blob',
1331
            'long nvarchar'            => 'text',
1332
            'long varbit'              => 'text',
1333
            'long varchar'             => 'text',
1334
            'money'                    => 'decimal',
1335
            'nchar'                    => 'string',
1336
            'ntext'                    => 'text',
1337
            'numeric'                  => 'decimal',
1338
            'nvarchar'                 => 'string',
1339
            'smalldatetime'            => 'datetime',
1340
            'smallint'                 => 'smallint',
1341
            'smallmoney'               => 'decimal',
1342
            'text'                     => 'text',
1343
            'time'                     => 'time',
1344
            'timestamp'                => 'datetime',
1345
            'timestamp with time zone' => 'datetime',
1346
            'tinyint'                  => 'smallint',
1347
            'uniqueidentifier'         => 'guid',
1348
            'uniqueidentifierstr'      => 'guid',
1349
            'unsigned bigint'          => 'bigint',
1350
            'unsigned int'             => 'integer',
1351
            'unsigned smallint'        => 'smallint',
1352
            'unsigned tinyint'         => 'smallint',
1353
            'varbinary'                => 'binary',
1354
            'varbit'                   => 'string',
1355
            'varchar'                  => 'string',
1356
            'xml'                      => 'text',
1357
        ];
1358
    }
1359
}
1360