Failed Conditions
Push — develop ( 776429...7c38e8 )
by Sergei
64:07 queued 10s
created

SQLAnywherePlatform::getTopClauseSQL()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3
1
<?php
2
3
namespace Doctrine\DBAL\Platforms;
4
5
use Doctrine\DBAL\DBALException;
6
use Doctrine\DBAL\LockMode;
7
use Doctrine\DBAL\Schema\Column;
8
use Doctrine\DBAL\Schema\ColumnDiff;
9
use Doctrine\DBAL\Schema\Constraint;
10
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
11
use Doctrine\DBAL\Schema\Identifier;
12
use Doctrine\DBAL\Schema\Index;
13
use Doctrine\DBAL\Schema\Sequence;
14
use Doctrine\DBAL\Schema\Table;
15
use Doctrine\DBAL\Schema\TableDiff;
16
use Doctrine\DBAL\TransactionIsolationLevel;
17
use InvalidArgumentException;
18
use UnexpectedValueException;
19
use function array_merge;
20
use function array_unique;
21
use function array_values;
22
use function count;
23
use function explode;
24
use function func_get_args;
25
use function get_class;
26
use function implode;
27
use function is_string;
28
use function preg_replace;
29
use function sprintf;
30
use function strlen;
31
use function strpos;
32
use function strtoupper;
33
use function substr;
34
35
/**
36
 * The SQLAnywherePlatform provides the behavior, features and SQL dialect of the
37
 * SAP Sybase SQL Anywhere 12 database platform.
38
 */
39
class SQLAnywherePlatform extends AbstractPlatform
40
{
41
    public const FOREIGN_KEY_MATCH_SIMPLE        = 1;
42
    public const FOREIGN_KEY_MATCH_FULL          = 2;
43
    public const FOREIGN_KEY_MATCH_SIMPLE_UNIQUE = 129;
44
    public const FOREIGN_KEY_MATCH_FULL_UNIQUE   = 130;
45
46
    /**
47
     * {@inheritdoc}
48
     */
49 161
    public function appendLockHint($fromClause, $lockMode)
50
    {
51 161
        switch (true) {
52
            case $lockMode === LockMode::NONE:
53 23
                return $fromClause . ' WITH (NOLOCK)';
54
55 138
            case $lockMode === LockMode::PESSIMISTIC_READ:
56 23
                return $fromClause . ' WITH (UPDLOCK)';
57
58 115
            case $lockMode === LockMode::PESSIMISTIC_WRITE:
59 23
                return $fromClause . ' WITH (XLOCK)';
60
61
            default:
62 92
                return $fromClause;
63
        }
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     *
69
     * SQL Anywhere supports a maximum length of 128 bytes for identifiers.
70
     */
71 23
    public function fixSchemaElementName($schemaElementName)
72
    {
73 23
        $maxIdentifierLength = $this->getMaxIdentifierLength();
74
75 23
        if (strlen($schemaElementName) > $maxIdentifierLength) {
76 23
            return substr($schemaElementName, 0, $maxIdentifierLength);
77
        }
78
79 23
        return $schemaElementName;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85 161
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
86
    {
87 161
        $query = '';
88
89 161
        if ($foreignKey->hasOption('match')) {
90 23
            $query = ' MATCH ' . $this->getForeignKeyMatchClauseSQL($foreignKey->getOption('match'));
91
        }
92
93 161
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
94
95 161
        if ($foreignKey->hasOption('check_on_commit') && (bool) $foreignKey->getOption('check_on_commit')) {
96 23
            $query .= ' CHECK ON COMMIT';
97
        }
98
99 161
        if ($foreignKey->hasOption('clustered') && (bool) $foreignKey->getOption('clustered')) {
100 23
            $query .= ' CLUSTERED';
101
        }
102
103 161
        if ($foreignKey->hasOption('for_olap_workload') && (bool) $foreignKey->getOption('for_olap_workload')) {
104 23
            $query .= ' FOR OLAP WORKLOAD';
105
        }
106
107 161
        return $query;
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113 345
    public function getAlterTableSQL(TableDiff $diff)
114
    {
115 345
        $sql          = [];
116 345
        $columnSql    = [];
117 345
        $commentsSQL  = [];
118 345
        $tableSql     = [];
119 345
        $alterClauses = [];
120
121
        foreach ($diff->addedColumns as $column) {
122 345
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
123 92
                continue;
124
            }
125
126
            $alterClauses[] = $this->getAlterTableAddColumnClause($column);
127 92
128
            $comment = $this->getColumnComment($column);
129 92
130
            if ($comment === null || $comment === '') {
131 92
                continue;
132 69
            }
133
134
            $commentsSQL[] = $this->getCommentOnColumnSQL(
135 23
                $diff->getName($this)->getQuotedName($this),
136 23
                $column->getQuotedName($this),
137 23
                $comment
138 23
            );
139
        }
140
141
        foreach ($diff->removedColumns as $column) {
142
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
143 345
                continue;
144 69
            }
145
146
            $alterClauses[] = $this->getAlterTableRemoveColumnClause($column);
147
        }
148 69
149
        foreach ($diff->changedColumns as $columnDiff) {
150
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
151
                continue;
152 345
            }
153 184
154
            $alterClause = $this->getAlterTableChangeColumnClause($columnDiff);
155
156
            if ($alterClause !== null) {
157 184
                $alterClauses[] = $alterClause;
158
            }
159 184
160 115
            if (! $columnDiff->hasChanged('comment')) {
161
                continue;
162
            }
163 184
164 115
            $column = $columnDiff->column;
165
166
            $commentsSQL[] = $this->getCommentOnColumnSQL(
167 69
                $diff->getName($this)->getQuotedName($this),
168
                $column->getQuotedName($this),
169 69
                $this->getColumnComment($column)
170 69
            );
171 69
        }
172 69
173
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
174
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
175
                continue;
176 345
            }
177 92
178
            $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' .
179
                $this->getAlterTableRenameColumnClause($oldColumnName, $column);
180
        }
181 92
182 92
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
183
            if (! empty($alterClauses)) {
184
                $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . implode(', ', $alterClauses);
185 345
            }
186 345
187 138
            $sql = array_merge($sql, $commentsSQL);
188
189
            if ($diff->newName !== false) {
190 345
                $sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' .
191
                    $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

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