Completed
Pull Request — develop (#3491)
by Sergei
64:09
created

SQLAnywherePlatform::getTopClauseSQL()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

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