Failed Conditions
Pull Request — develop (#3348)
by Sergei
38:24 queued 35:29
created

SQLAnywherePlatform::getCommentOnColumnSQL()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 2
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 get_class;
25
use function implode;
26
use function is_string;
27
use function preg_replace;
28
use function sprintf;
29
use function strlen;
30
use function strpos;
31
use function strtoupper;
32
use function substr;
33
34
/**
35
 * The SQLAnywherePlatform provides the behavior, features and SQL dialect of the
36
 * SAP Sybase SQL Anywhere 12 database platform.
37
 */
38
class SQLAnywherePlatform 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
    public function appendLockHint(string $fromClause, ?int $lockMode) : string
49
    {
50
        switch (true) {
51 133
            case $lockMode === LockMode::NONE:
52
                return $fromClause . ' WITH (NOLOCK)';
53 133
54
            case $lockMode === LockMode::PESSIMISTIC_READ:
55 19
                return $fromClause . ' WITH (UPDLOCK)';
56
57 114
            case $lockMode === LockMode::PESSIMISTIC_WRITE:
58 19
                return $fromClause . ' WITH (XLOCK)';
59
60 95
            default:
61 19
                return $fromClause;
62
        }
63
    }
64 76
65
    /**
66
     * {@inheritdoc}
67
     *
68
     * SQL Anywhere supports a maximum length of 128 bytes for identifiers.
69
     */
70
    public function fixSchemaElementName($schemaElementName) : string
71
    {
72
        $maxIdentifierLength = $this->getMaxIdentifierLength();
73 19
74
        if (strlen($schemaElementName) > $maxIdentifierLength) {
75 19
            return substr($schemaElementName, 0, $maxIdentifierLength);
76
        }
77 19
78 19
        return $schemaElementName;
79
    }
80
81 19
    /**
82
     * {@inheritdoc}
83
     */
84
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string
85
    {
86
        $query = '';
87 133
88
        if ($foreignKey->hasOption('match')) {
89 133
            $query = ' MATCH ' . $this->getForeignKeyMatchClauseSQL($foreignKey->getOption('match'));
90
        }
91 133
92 19
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
93
94
        if ($foreignKey->hasOption('check_on_commit') && (bool) $foreignKey->getOption('check_on_commit')) {
95 133
            $query .= ' CHECK ON COMMIT';
96
        }
97 133
98 19
        if ($foreignKey->hasOption('clustered') && (bool) $foreignKey->getOption('clustered')) {
99
            $query .= ' CLUSTERED';
100
        }
101 133
102 19
        if ($foreignKey->hasOption('for_olap_workload') && (bool) $foreignKey->getOption('for_olap_workload')) {
103
            $query .= ' FOR OLAP WORKLOAD';
104
        }
105 133
106 19
        return $query;
107
    }
108
109 133
    /**
110
     * {@inheritdoc}
111
     */
112
    public function getAlterTableSQL(TableDiff $diff)
113
    {
114
        $sql          = [];
115 285
        $columnSql    = [];
116
        $commentsSQL  = [];
117 285
        $tableSql     = [];
118 285
        $alterClauses = [];
119 285
120 285
        /** @var Column $column */
121 285
        foreach ($diff->addedColumns as $column) {
122
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
123
                continue;
124 285
            }
125 76
126
            $alterClauses[] = $this->getAlterTableAddColumnClause($column);
127
128
            $comment = $this->getColumnComment($column);
129 76
130
            if ($comment === null || $comment === '') {
131 76
                continue;
132
            }
133 76
134 57
            $commentsSQL[] = $this->getCommentOnColumnSQL(
135
                $diff->getName($this)->getQuotedName($this),
136
                $column->getQuotedName($this),
137 19
                $comment
138 19
            );
139 19
        }
140 19
141
        /** @var Column $column */
142
        foreach ($diff->removedColumns as $column) {
143
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
144
                continue;
145 285
            }
146 57
147
            $alterClauses[] = $this->getAlterTableRemoveColumnClause($column);
148
        }
149
150 57
        /** @var ColumnDiff $columnDiff */
151
        foreach ($diff->changedColumns as $columnDiff) {
152
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
153
                continue;
154 285
            }
155 152
156
            $alterClause = $this->getAlterTableChangeColumnClause($columnDiff);
157
158
            if ($alterClause !== null) {
159 152
                $alterClauses[] = $alterClause;
160
            }
161 152
162 95
            if (! $columnDiff->hasChanged('comment')) {
163
                continue;
164
            }
165 152
166 95
            $column = $columnDiff->column;
167
168
            $commentsSQL[] = $this->getCommentOnColumnSQL(
169 57
                $diff->getName($this)->getQuotedName($this),
170
                $column->getQuotedName($this),
171 57
                $this->getColumnComment($column)
172 57
            );
173 57
        }
174 57
175
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
176
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
177
                continue;
178 285
            }
179 76
180
            $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' .
181
                $this->getAlterTableRenameColumnClause($oldColumnName, $column);
182
        }
183 76
184 76
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
185
            if (! empty($alterClauses)) {
186
                $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . implode(', ', $alterClauses);
187 285
            }
188 285
189 114
            $sql = array_merge($sql, $commentsSQL);
190
191
            if ($diff->newName !== false) {
192 285
                $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' .
193
                    $this->getAlterTableRenameTableClause($diff->getNewName());
0 ignored issues
show
Bug introduced by
It seems like $diff->getNewName() can also be of type string; however, parameter $newTableName of Doctrine\DBAL\Platforms\...ableRenameTableClause() does only seem to accept Doctrine\DBAL\Schema\Identifier, maybe add an additional type check? ( Ignorable by Annotation )

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

193
                    $this->getAlterTableRenameTableClause(/** @scrutinizer ignore-type */ $diff->getNewName());
Loading history...
194 285
            }
195 38
196 38
            $sql = array_merge(
197
                $this->getPreAlterTableIndexForeignKeySQL($diff),
198
                $sql,
199 285
                $this->getPostAlterTableIndexForeignKeySQL($diff)
200 285
            );
201 285
        }
202 285
203
        return array_merge($sql, $tableSql, $columnSql);
204
    }
205
206 285
    /**
207
     * Returns the SQL clause for creating a column in a table alteration.
208
     *
209
     * @param Column $column The column to add.
210
     */
211
    protected function getAlterTableAddColumnClause(Column $column) : string
212
    {
213
        return 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
214
    }
215
216 76
    /**
217
     * Returns the SQL clause for altering a table.
218 76
     *
219
     * @param Identifier $tableName The quoted name of the table to alter.
220
     */
221
    protected function getAlterTableClause(Identifier $tableName) : string
222
    {
223
        return 'ALTER TABLE ' . $tableName->getQuotedName($this);
224
    }
225
226
    /**
227
     * Returns the SQL clause for dropping a column in a table alteration.
228 152
     *
229
     * @param Column $column The column to drop.
230 152
     */
231
    protected function getAlterTableRemoveColumnClause(Column $column) : string
232
    {
233
        return 'DROP ' . $column->getQuotedName($this);
234
    }
235
236
    /**
237
     * Returns the SQL clause for renaming a column in a table alteration.
238
     *
239
     * @param string $oldColumnName The quoted name of the column to rename.
240 57
     * @param Column $column        The column to rename to.
241
     */
242 57
    protected function getAlterTableRenameColumnClause(string $oldColumnName, Column $column) : string
243
    {
244
        $oldColumnName = new Identifier($oldColumnName);
245
246
        return 'RENAME ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this);
247
    }
248
249
    /**
250
     * Returns the SQL clause for renaming a table in a table alteration.
251
     *
252
     * @param Identifier $newTableName The quoted name of the table to rename to.
253 76
     */
254
    protected function getAlterTableRenameTableClause(Identifier $newTableName) : string
255 76
    {
256
        return 'RENAME ' . $newTableName->getQuotedName($this);
257 76
    }
258
259
    /**
260
     * Returns the SQL clause for altering a column in a table alteration.
261
     *
262
     * This method returns null in case that only the column comment has changed.
263
     * Changes in column comments have to be handled differently.
264
     *
265
     * @param ColumnDiff $columnDiff The diff of the column to alter.
266
     */
267 38
    protected function getAlterTableChangeColumnClause(ColumnDiff $columnDiff) : ?string
268
    {
269 38
        $column = $columnDiff->column;
270
271
        // Do not return alter clause if only comment has changed.
272
        if (! ($columnDiff->hasChanged('comment') && count($columnDiff->changedProperties) === 1)) {
273
            $columnAlterationClause = 'ALTER ' .
274
                $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
275
276
            if ($columnDiff->hasChanged('default') && $column->getDefault() === null) {
277
                $columnAlterationClause .= ', ALTER ' . $column->getQuotedName($this) . ' DROP DEFAULT';
278
            }
279
280
            return $columnAlterationClause;
281
        }
282 152
283
        return null;
284 152
    }
285
286
    /**
287 152
     * {@inheritdoc}
288
     */
289 95
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
290
    {
291 95
        $columnDef['integer_type'] = 'BIGINT';
292
293
        return $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
294
    }
295 95
296
    /**
297
     * {@inheritdoc}
298 57
     */
299
    public function getBinaryDefaultLength() : int
300
    {
301
        return 1;
302
    }
303
304 19
    /**
305
     * {@inheritdoc}
306 19
     */
307
    public function getBinaryMaxLength() : int
308 19
    {
309
        return 32767;
310
    }
311
312
    /**
313
     * {@inheritdoc}
314 38
     */
315
    public function getBlobTypeDeclarationSQL(array $field) : string
316 38
    {
317
        return 'LONG BINARY';
318
    }
319
320
    /**
321
     * {@inheritdoc}
322 19
     *
323
     * BIT type columns require an explicit NULL declaration
324 19
     * in SQL Anywhere if they shall be nullable.
325
     * Otherwise by just omitting the NOT NULL clause,
326
     * SQL Anywhere will declare them NOT NULL nonetheless.
327
     */
328
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
329
    {
330 19
        $nullClause = isset($columnDef['notnull']) && (bool) $columnDef['notnull'] === false ? ' NULL' : '';
331
332 19
        return 'BIT' . $nullClause;
333
    }
334
335
    /**
336
     * {@inheritdoc}
337
     */
338
    public function getClobTypeDeclarationSQL(array $field) : string
339
    {
340
        return 'TEXT';
341
    }
342
343 38
    /**
344
     * {@inheritdoc}
345 38
     */
346
    public function getConcatExpression(string ...$expressions) : string
347 38
    {
348
        return 'STRING(' . implode(', ', $expressions) . ')';
349
    }
350
351
    /**
352
     * {@inheritdoc}
353 57
     */
354
    public function getCreateConstraintSQL(Constraint $constraint, $table) : string
355 57
    {
356
        if ($constraint instanceof ForeignKeyConstraint) {
357
            return $this->getCreateForeignKeySQL($constraint, $table);
358
        }
359
360
        if ($table instanceof Table) {
361 152
            $table = $table->getQuotedName($this);
362
        }
363 152
364 152
        return 'ALTER TABLE ' . $table .
365 152
               ' ADD ' . $this->getTableConstraintDeclarationSQL($constraint, $constraint->getQuotedName($this));
366
    }
367 152
368 152
    /**
369 152
     * {@inheritdoc}
370 152
     */
371 152
    public function getCreateDatabaseSQL($database) : string
372
    {
373
        $database = new Identifier($database);
374
375
        return "CREATE DATABASE '" . $database->getName() . "'";
376
    }
377
378 19
    /**
379
     * {@inheritdoc}
380 19
     *
381
     * Appends SQL Anywhere specific flags if given.
382
     */
383
    public function getCreateIndexSQL(Index $index, $table) : string
384
    {
385
        return parent::getCreateIndexSQL($index, $table) . $this->getAdvancedIndexOptionsSQL($index);
386 57
    }
387
388 57
    /**
389 19
     * {@inheritdoc}
390
     */
391
    public function getCreatePrimaryKeySQL(Index $index, $table) : string
392 57
    {
393 19
        if ($table instanceof Table) {
394
            $table = $table->getQuotedName($this);
395
        }
396 57
397 57
        return 'ALTER TABLE ' . $table . ' ADD ' . $this->getPrimaryKeyDeclarationSQL($index);
398
    }
399
400
    /**
401
     * {@inheritdoc}
402
     */
403 19
    public function getCreateTemporaryTableSnippetSQL() : string
404
    {
405 19
        return 'CREATE ' . $this->getTemporaryTableSQL() . ' TABLE';
406
    }
407 19
408
    /**
409
     * {@inheritdoc}
410
     */
411
    public function getCreateViewSQL($name, $sql) : string
412
    {
413
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
414
    }
415 171
416
    /**
417 171
     * {@inheritdoc}
418
     */
419
    public function getCurrentDateSQL() : string
420
    {
421
        return 'CURRENT DATE';
422
    }
423 38
424
    /**
425 38
     * {@inheritdoc}
426 19
     */
427
    public function getCurrentTimeSQL() : string
428
    {
429 38
        return 'CURRENT TIME';
430
    }
431
432
    /**
433
     * {@inheritdoc}
434
     */
435 19
    public function getCurrentTimestampSQL() : string
436
    {
437 19
        return 'CURRENT TIMESTAMP';
438
    }
439
440
    /**
441
     * {@inheritdoc}
442
     */
443 19
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
444
    {
445 19
        $factorClause = '';
446
447
        if ($operator === '-') {
448
            $factorClause = '-1 * ';
449
        }
450
451 38
        return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')';
452
    }
453 38
454
    /**
455
     * {@inheritdoc}
456
     */
457
    public function getDateDiffExpression($date1, $date2) : string
458
    {
459 19
        return 'DATEDIFF(day, ' . $date2 . ', ' . $date1 . ')';
460
    }
461 19
462
    /**
463
     * {@inheritdoc}
464
     */
465
    public function getDateTimeFormatString() : string
466
    {
467 38
        return 'Y-m-d H:i:s.u';
468
    }
469 38
470
    /**
471
     * {@inheritdoc}
472
     */
473
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
474
    {
475 19
        return 'DATETIME';
476
    }
477 19
478
    /**
479 19
     * {@inheritdoc}
480 19
     */
481
    public function getDateTimeTzFormatString() : string
482
    {
483 19
        return 'Y-m-d H:i:s.uP';
484
    }
485
486
    /**
487
     * {@inheritdoc}
488
     */
489 19
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
490
    {
491 19
        return 'DATE';
492
    }
493
494
    /**
495
     * {@inheritdoc}
496
     */
497 19
    public function getDefaultTransactionIsolationLevel() : int
498
    {
499 19
        return TransactionIsolationLevel::READ_UNCOMMITTED;
500
    }
501
502
    /**
503
     * {@inheritdoc}
504
     */
505 19
    public function getDropDatabaseSQL($database) : string
506
    {
507 19
        $database = new Identifier($database);
508
509
        return "DROP DATABASE '" . $database->getName() . "'";
510
    }
511
512
    /**
513 19
     * {@inheritdoc}
514
     */
515 19
    public function getDropIndexSQL($index, $table = null) : string
516
    {
517
        if ($index instanceof Index) {
518
            $index = $index->getQuotedName($this);
519
        }
520
521 19
        if (! is_string($index)) {
522
            throw new InvalidArgumentException(
523 19
                'SQLAnywherePlatform::getDropIndexSQL() expects $index parameter to be string or ' . Index::class . '.'
524
            );
525
        }
526
527
        if (! isset($table)) {
528
            return 'DROP INDEX ' . $index;
529 19
        }
530
531 19
        if ($table instanceof Table) {
532
            $table = $table->getQuotedName($this);
533
        }
534
535
        if (! is_string($table)) {
536
            throw new InvalidArgumentException(
537 19
                'SQLAnywherePlatform::getDropIndexSQL() expects $table parameter to be string or ' . Index::class . '.'
538
            );
539 19
        }
540
541 19
        return 'DROP INDEX ' . $table . '.' . $index;
542
    }
543
544
    /**
545
     * {@inheritdoc}
546
     */
547 57
    public function getDropViewSQL($name) : string
548
    {
549 57
        return 'DROP VIEW ' . $name;
550 19
    }
551
552
    /**
553 57
     * {@inheritdoc}
554 19
     */
555 19
    public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) : string
556
    {
557
        $sql              = '';
558
        $foreignKeyName   = $foreignKey->getName();
559 38
        $localColumns     = $foreignKey->getQuotedLocalColumns($this);
560 19
        $foreignColumns   = $foreignKey->getQuotedForeignColumns($this);
561
        $foreignTableName = $foreignKey->getQuotedForeignTableName($this);
562
563 38
        if (! empty($foreignKeyName)) {
564 19
            $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' ';
565
        }
566
567 38
        if (empty($localColumns)) {
568 19
            throw new InvalidArgumentException("Incomplete definition. 'local' required.");
569 19
        }
570
571
        if (empty($foreignColumns)) {
572
            throw new InvalidArgumentException("Incomplete definition. 'foreign' required.");
573 19
        }
574
575
        if (empty($foreignTableName)) {
576
            throw new InvalidArgumentException("Incomplete definition. 'foreignTable' required.");
577
        }
578
579 19
        if ($foreignKey->hasOption('notnull') && (bool) $foreignKey->getOption('notnull')) {
580
            $sql .= 'NOT NULL ';
581 19
        }
582
583
        return $sql .
584
            'FOREIGN KEY (' . $this->getIndexFieldDeclarationListSQL($localColumns) . ') ' .
585
            'REFERENCES ' . $foreignKey->getQuotedForeignTableName($this) .
586
            ' (' . $this->getIndexFieldDeclarationListSQL($foreignColumns) . ')';
587 190
    }
588
589 190
    /**
590 190
     * Returns foreign key MATCH clause for given type.
591 190
     *
592 190
     * @param int $type The foreign key match type
593 190
     *
594
     * @throws InvalidArgumentException If unknown match type given.
595 190
     */
596 114
    public function getForeignKeyMatchClauseSQL(int $type) : string
597
    {
598
        switch ((int) $type) {
599 190
            case self::FOREIGN_KEY_MATCH_SIMPLE:
600 19
                return 'SIMPLE';
601
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

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

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

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

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