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

SQLAnywhere16Platform::getAlterSequenceSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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