Completed
Pull Request — master (#3610)
by Sergei
03:03
created

SQLAnywherePlatform::getListViewsSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1471 9744
            'bigint'                   => 'bigint',
1472
            'binary'                   => 'binary',
1473
            'bit'                      => 'boolean',
1474
            'char'                     => 'string',
1475
            'decimal'                  => 'decimal',
1476
            'date'                     => 'date',
1477
            'datetime'                 => 'datetime',
1478
            'double'                   => 'float',
1479
            'float'                    => 'float',
1480
            'image'                    => 'blob',
1481
            'int'                      => 'integer',
1482
            'integer'                  => 'integer',
1483
            'long binary'              => 'blob',
1484
            'long nvarchar'            => 'text',
1485
            'long varbit'              => 'text',
1486
            'long varchar'             => 'text',
1487
            'money'                    => 'decimal',
1488
            'nchar'                    => 'string',
1489
            'ntext'                    => 'text',
1490
            'numeric'                  => 'decimal',
1491
            'nvarchar'                 => 'string',
1492
            'smalldatetime'            => 'datetime',
1493
            'smallint'                 => 'smallint',
1494
            'smallmoney'               => 'decimal',
1495
            'text'                     => 'text',
1496
            'time'                     => 'time',
1497
            'timestamp'                => 'datetime',
1498
            'timestamp with time zone' => 'datetime',
1499
            'tinyint'                  => 'smallint',
1500
            'uniqueidentifier'         => 'guid',
1501
            'uniqueidentifierstr'      => 'guid',
1502
            'unsigned bigint'          => 'bigint',
1503
            'unsigned int'             => 'integer',
1504
            'unsigned smallint'        => 'smallint',
1505
            'unsigned tinyint'         => 'smallint',
1506
            'varbinary'                => 'binary',
1507
            'varbit'                   => 'string',
1508
            'varchar'                  => 'string',
1509
            'xml'                      => 'text',
1510
        ];
1511 9744
    }
1512
}
1513