Failed Conditions
Pull Request — master (#3260)
by Michael
61:30
created

SQLAnywherePlatform::getTrimExpression()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7

Importance

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