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