Failed Conditions
Pull Request — 3.0.x (#3980)
by Guilherme
06:55
created

getUniqueConstraintDeclarationSQL()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4.125

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 2
dl 0
loc 16
ccs 4
cts 8
cp 0.5
crap 4.125
rs 10
c 0
b 0
f 0

1 Method

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